Closure Compilerの型テンプレートが進化してる件
altjsガチ勢のみなさんこんにちは。
TypeScriptの次期バージョンでジェネリクスが入るぜ!なんて話題になってたりしますが、Closure Compilerのテンプレートまわりも人知れず進化しているので紹介。
なぜかドキュメントにはさっぱり載ってないので、Compilerのソースとテストコードが命です。
メソッドテンプレート
去年サポートされたのがメソッドテンプレート。メソッドや関数のアノテーションで@template
が使えます。
次のサンプルコードはArrayから特定条件を満たす要素を探して返す関数。Arrayの要素の型と条件関数f
のthis
にバインドされるopt_this
の2つがテンプレートになってます。
/** * @param {Array.<T>} arr * @param {function(this: S, T, number, Array.<T>): boolean} f * @param {S=} opt_this * @return {?T} * @template T,S */ function find(arr, f, opt_this) { for (var i = 0; i < arr.length; i++) { if (f.call(opt_this, arr[i], i, arr)) { return arr[i]; } } return null; } /** @type {Array.<number>} */ var a = [10, 20, 30]; var b = find(a, function(item, index, arr) { return item > 20; }); /** @type {string} */ var c = b; // Type Error! Required {string} but found {number}
テンプレートのおかげでb
はnumber
で型付けされるので、string
型のc
に代入しようとすると型エラーになるわけです。
このへんの使い方は、Closure Libraryのgoog.arrayとかgoog.objectのコードが参考になります。
クラステンプレート
続いて今月のリリースでサポートされたクラステンプレート。 オンラインの Closure Compiler Service はまだ最新じゃないので、クラステンプレートを試したい人はローカルで動かしてください。
あんまり意味無いコードですけどサンプル。
/** * @param {T} prop * @template T * @constructor */ var Foo = function(prop) { /** * @type {T} * @private */ this.prop_ = prop; }; /** * @return {T} */ Foo.prototype.getProp = function() { return this.prop_; }; /** @type {Foo.<number>} */ var foo = new Foo(100); /** @type {string} */ var bar = foo.getProp(); // Type Error! Required {string} but found {number}
アノテーションでFoo.<number>
型を宣言してるので、foo.getProp()
の戻り値の型はnumber
になるわけです。よってstring
型のbar
に代入するところで正しく型エラー。
newのところの型宣言アノテーションは必須。型推論がもうちょい効いてくれるとうれしいですね。
インターフェイスも同じような感じ。
/** * @template T * @interface */ var A = function() {}; /** * @implements {A.<number>} * @constructor */ var B = function() {}; /** @type {A.<number>} */ var b1 = new B(); // OK /** @type {A.<?>} */ var b2 = new B(); // OK /** @type {B} */ var b3 = new B(); // OK /** @type {A.<string>} */ var b4 = new B(); // Type Error! Required {A.<string>} but found {A.<number>}
A<?>
みたいなワイルドカードでも受けられるけど、型変数の中身が違ってたらちゃんとエラーになります。
という感じで、1年ちょっと前はテンプレートはなぜかthis
にしかバインドできないという謎仕様だったのがだいぶ進化してきました。
@extends {A.<string>}
とか@implements {A.<string>}
みたいにクラスの型変数をバインドしても、親クラスのメソッドの型変数に反映されないのが微妙なので、このあたりがうまくいけば満足できそうです。あと型推論か。