TypeScriptで組み込みオブジェクトを拡張できない

最近、自分で書く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.hypotMath.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 等の組み込みオブジェクトも同じ問題を抱えている。

ググってみたらすでに同じ問題で悩んでいる人がいた。

最初のStack Overflowの回答によると、「自前の lib.d.ts を用意しろ」らしい。つらい。

[2015年1月18日 追記] 以上の問題点はTypeScript 1.4のリリースにより解決しました。詳しくはこちら


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です