最近、自分で書くWebアプリ、ブラウザアプリには、JavaScriptの代替言語としてTypeScriptを使っている。
TypeScriptの気に入っている点は、
- 静的型がついているので、変数名のタイポのような、初歩的だけど実行してみないとわからない面倒なバグをコンパイル時に検出できる
- 文法やセマンティクスがほぼJavaScriptの上位互換なので、すでにJavaScriptを習得している身としては学習コストが低い
- JavaScriptの資産を活用しやすい
- 標準の型定義に入っていないブラウザの拡張API等にも、自分で型定義を書けば利用できる
あたりである。逆に、不満があるとすれば
- セマンティクスがJavaScriptに沿ったものなので、演算子オーバーロードのようなJavaSciptに1:1で翻訳できない機能が使えない
- 型クラスとかがない
- ECMAScript 6で導入される予定の、letや分割代入やジェネレーターなどの、言語の文法に対する拡張に対応していない(新しいラムダ式のような一部の機能は既に導入されているので、将来的には入るかもしれないが)
あたりであろうか。まあ不満があったら他のaltJSを使えばいい話だが。
[2015年1月18日 追記] 以下の内容はTypeScript 1.4のリリースに伴い古くなりました。詳しくはこちら。
ECMAScript 6で導入されるライブラリ関数は、一部は型宣言を書いてやることでTypeScriptからも使えるようになる。例えば、前の記事で Math.hypot
や Math.sinh
などの関数に触れたが、それらは
interface Math { hypot(...values: number[]): number; sinh(x: number): number; }
のような宣言を書いておけば、コンパイルが通るようになる。まだこれらの関数を実装していないブラウザ用に、polyfill(shim)も書いておくか、読み込むといいだろう。
このほか、例えば、Array.prototype.find
の型宣言は
interface Array<T> { find(callback: (element: T, index: number, array: T[]) => boolean, thisArg?: any): T; }
というふうにできる。
ここまではいい。
JavaScriptのイディオムとして、配列ライクなオブジェクトを変換するのに、Array.prototype.slice
が使われる。もちろんこのイディオムはTypeScriptでも使えるが、Array.prototype
の型は Array<any>
となっているので、要素の型に対する型チェックや型推論が働いてくれない。幸い、ECMAScript 6では、Array.from
という関数が導入される予定なので、こいつの型を
from<T>(arrayLike: {[n: number]: T; length: number}): T[];
のように宣言してやれば型推論とかが効いて便利なはずだ。
だがしかし。組み込みの Array
オブジェクトの型は標準の lib.d.ts
において
declare var Array: { new (arrayLength?: number): any[]; ... prototype: Array<any>; }
のように宣言されている。こいつに from
メソッドを追加しようとして自分のコードで
declare var Array: { from<T>(arrayLike: {[n: number]: T; length: number}): T[]; }
と書くとエラーが出る。既存のメソッドも書かないとダメかと思って
declare var Array: { new (arrayLength?: number): any[]; ... prototype: Array<any>; from<T>(arrayLike: {[n: number]: T; length: number}): T[]; }
と書いてもエラーになる。モジュールならばどうかと思って
module Array { from<T>(arrayLike: {[n: number]: T; length: number}): T[]; }
と書いてもエラーになる。組み込みの Array
オブジェクトの型は lib.d.ts
で書かれたものから変更できないのだ。
だがちょっと待て。Math
オブジェクトは自前で拡張できたではないか。これは何でだったかというと、Math
オブジェクトは
interface Math { ... } declare var Math: Math;
という風に定義されていて、Math
インターフェースにメソッドを追加すれば Math
オブジェクトを拡張できたのだ。Array
も
interface ArrayConstructor { new (arrayLength?: number): any[]; ... prototype: Array<any>; } declare var Array: ArrayConstructor;
と定義されていれば良かったのに。Array
だけではなくて、Object
, Number
, String
等の組み込みオブジェクトも同じ問題を抱えている。
ググってみたらすでに同じ問題で悩んでいる人がいた。
- javascript – Polyfills, Shims & Extensions with TypeScript – Stack Overflow
- javascript – Extending instance/static functions on existing prototypes with TypeScript – Stack Overflow
- TypeScript – View Issue #917: Make class / variable declarations open ended
- Use static interfaces for Ambient declarations in lib.d.ts · Issue #182 · Microsoft/TypeScript
最初のStack Overflowの回答によると、「自前の lib.d.ts
を用意しろ」らしい。つらい。
[2015年1月18日 追記] 以上の問題点はTypeScript 1.4のリリースにより解決しました。詳しくはこちら。