linq.jsのTypeScript対応とTypeScript雑感

MicrosoftからTypeScriptという新言語が発表されました。驚くべきは、あのC#のAnders Hejlsbergが関わっている!これはもう触るしかない。そしてこれはコンパイル後にJavaScriptになる言語(CoffeeとかJSXとかみたいな)なわけで、じゃあlinq.jsを対応させるしかない!というわけで、させました。

// TypeScript
Enumerable.range(1, 10)
    .where(x => x % 2 == 0)
    .select(x => x * x)
    .writeLine();

// コンパイル後
Enumerable.range(1, 10).where(function (x) {
    return x % 2 == 0;
}).select(function (x) {
    return x * x;
}).writeLine();

ひゃっはー、もうfunction() { return }とはオサラバだ!そしてこの記述性と最強のコレクション操作であるLINQが合わさると最強に見える。

に型定義ファイルは同梱してありますので、是非是非お試しを。NuGetのlinq.js -Preでも入ります。NPMは予定はありますが、まだです、すみません。

TypeScriptについて

型安全なCoffee Script、といった印象ですね。基本的にはCoffee Scriptに近いと思います。JavaScriptにプラスアルファな構文を採用することで、既存のJSライブラリとの繋がりを良くすることと、綺麗なJavaScriptを吐くことに重きが置かれている。TypeScriptは、比較的素直にJavaScriptに読み解くことが出来て、独自のコード生成は、現状はほぼほぼ無いし、意図的に省かれているようです(例えば非同期にたいしてasync構文を入れたりすると、大量のコード生成が入り、出力されるJavaScriptが機械的に汚れがち)。

そういった点、機能面では、TypeScriptには物足りなさを感じるところが多いかもしれません。じゃあJavaScriptに対する強みってどこなんだよ!といったら、一つはJavaScriptの冗長な記述性の補正(class,module, arrow function, Object.create/definePropertyとかも最低だしね)。もう一つは、無理なく自然に馴染んだ型付け。

型はないよりあったほうがいい。でも、型を付けるのがあまりにも苦痛だったら?ちょっとしたコードを書くのにも型!型!型!と押し付けられたら?そりゃあ、嫌だ。というわけで、型推論によって、比較的スムースに書けるようになっています。

型推論の性質というか範囲というか強さというかは、C#と非常に近いというかC#とまるで一緒なので、C#erならサクッと馴染めます。もっと強力な型推論のある言語に馴染んでいる人だと、え、ここで効かないの?みたいな違和感はあるかもですが。

また、さすがはMicrosoftというかAnders Hejlsbergというか、入力補完のことを念頭に置いた言語設計になっているので、IDEとの相性が非常に良い。そして最初からVisual StudioによるIDE環境が用意されていることで、型のある利点の一つであるリアルタイムエラー通知や入力補完をたっぷり満喫できます。さらに、それはTypeScript PlaygroundによってWeb上でも体感できます。というか、もはやPlaygroundはWeb IDEとでも言うべき驚異的な動き!

また、Windowsだけではなく、最初からSublime, Vim, Emacsの対応ファイルが用意されているというところからも、Windowsに限らず幅広く請求したい、という表れだと思います。そして実際、言語はプラットフォーム中立なわけです(最終的にはJavaScriptだしね!)。

Structural Subtyping

TypeScriptの最も面白いところは、ここです。C#とかのインターフェイスとLLのダックタイピングの中間、みたいな。実にゆるふわなJavaScriptと絶妙に合っててイイ!というかそもそも私はStructural Subtypingって名前だけでぜーんぜん分かってなかったのですが、TypeScriptだと自然と馴染めます。ほむ、どういうことか、というと、一例を。

union(second: any[], compareSelector?: (element: any) => any): Enumerable;
union(second: Enumerable, compareSelector?: (element: any) => any): Enumerable;
union(second: { length: number;[index: number]: any; }, compareSelector?: (element: any) => any): Enumerable;

これはlinq.jsの型定義の一つでunion、和集合を生成するためのメソッドです。なので、元シーケンスと、対象シーケンス(second)が対象になるわけですが、じゃあシーケンスって何?と。列挙できればいいので、配列もそうだし、Enumerableもそう。そして、JavaScriptに特有の存在として、配列みたいだけど配列じゃあないもの(lengthを持っていてインデクサでアクセスできる)、例えばDOMのNodeListとか、もそう。

で、そういった「lengthを持っていてインデクサでアクセスできる」という型の定義が{ length: number;[index: number]: any; }。これにより、DOMのNodeListやjQuery([0]とかでHTMLElementが取れる)など、配列みたいだけど配列じゃないもの全てが型安全に定義されました。やったね!

もしC#だったら、対象はインターフェイスで指定するしかないので、IEnumerable<T>を実装していないクソコレクションクラスが存在したら、それは列挙不能になってしまいます。片っ端からオーバーロードを作るのは不可能だし、かといってdynamic secondなどとしてしまってはアレ。

とはいえ、基本的にC#では最初から全てのシーケンスはIEnumerable<T>を実装している、という前提が成り立っているので、問題はおこらない。でも、JavaScriptは違う。配列みたいだけど配列じゃあないもの、が跋扈してる。でも、そこをanyとして何でも受け入れられるようにしたら型安全じゃあない。安全にしたい。そこをStructural Subtypingが華麗に解決してくれました!惚れた……。

TypeScriptはJavaScriptか?

JavaScriptコードはそのままTypeScriptだ!ということにYesと言えるかというと、イエスでもあり、しかし割とノーです。私がこの話を聞いて、最初に思ったのは、既存JSコード、つまりライブラリ類もそのままで動くのかな?と。答えはNOです。JS固有の、実行時に切った貼ったして構造作っていくの、ああいうのTypeScriptだと軒並みコンパイルエラーになるので、ダメです。ほとんどのライブラリが絶滅でしょう。

と、勘違いしていたのですが(yharaさん指摘ありがとうございます!)

declare var $; // jQuery
declare var _; // underscore
declare var Enumerable; // linq.js

とかって定義すると、これはそれぞれ $:any, _:any, Enumerable:any という扱いになって、以降はどんなチェーンを繋げてもエラーが起こらない、つまりライブラリが正常に読み込めたかのようになります。

ただ、型チェックや入力補完が効かなくなるので、TypeScript用の型注釈ファイルはあったほうがいいですね。有名ライブラリはともあれ、無名プラグインとかは自前で型注釈書かなければならないかもり。手書きだとかったるいので、自動生成である程度テンプレート吐き出してくれないと、面倒くさい。この辺はMicrosoftだしやってくれるんじゃないかなあ、という淡い期待を抱いていますが……。

とはいえ、ちょっとしたコンパクトなプラグインを使ったり、ライブラリ使うとしても一部分だけだしー、などというのに、わざわざ型定義も馬鹿らしいわけで、さくっと動的な感じにdeclareできちゃう、というのは素晴らしい話。

そんなわけで、JavaScript→TypeScriptの相互運用性としては、繋がりはかなり良好。勿論、jQueryなどもスムースに扱うことができます。これは、文法がJavaScriptプラスアルファで構築されているがことの利点です。そしてTypeScript→JavaScriptは、というと、素直なJavaScriptを吐いてくれることもあり、良好です。TypeScriptで作られた生成物は、TypeScriptだけに閉じません。

JavaScriptを中間言語とする選択肢が増えた。JavaScriptを介することで他の言語とも自由に繋がる。ここには、Webの互換性、中立性を崩す要素は一切ありません。独自言語による囲い込みとかではありません。素直に歓迎できるはずです。ただし、言語としてはあくまでTypeScriptはTypeScriptです。そこだけは、誤解しないほうがいいと思います。文法的に、ES6を若干取り入れているとはいえ、違う言語です。将来的にもTypeScriptはEcmaScriptにならないでしょうし、EcmaScriptはTypeScriptにはならないでしょう。TypeScriptはEcmaScript6のただの代替なのではない、別の価値ある言語です。

変な期待をして、これJavaScriptじゃないじゃん、とかって難癖つけたりは、あまり良くないですね。

TypeScriptとVisual Studio

別にMicrosoft環境に閉じる言語ではないので、EmacsでもVimでもいいですが、やはりVisual Studioが第一な点は少なからずあります。LinuxでもIDEで書きたい?きっとJetBrainsがWebStormに搭載してくれるはずです!(実際、Voteはかなり集まってました)

ともあれ、Visual Studioです。専用拡張のインストールはTypeScriptのサイトから出来ます。プロジェクトテンプレートが何故かC#のところにあって気づきにくいことに注意!それともう一つ、Web Essentialsを入れましょう。元々Coffee ScriptとLESSに対応していたのですが、今回TypeScriptにも対応してくれました。Web Essentialsを入れることで、保存時のコンパイル(通常の拡張だとビルド時のみ)と、ウィンドウ分割での出力後のJS表示、それとSourceMapファイルの出力を行ってくれます。

勿論、IntelliSenseはフルに効くしエラーはリアルタイムでがんがん通知してくれます。TypeScript Playgroundと違うのは、エラーがあるとJSに変換してくれないところですね。まあ、それは正しい挙動なのでいいです。Playgroundで中途半端なエラーのある状態でもガンガン変更表示してくれるのは、それはそれで便利なので、それもまたいいです。

ちなみに、TypeScript Playgroundでは赤波線が出ている状態は、一応、JSを出力してくれてますが、それはコンパイルエラーの状態で完全な出力がされていないと思って良いです。つまり、本来的には動いてないわけです。この動いていない出力を指して、(現状Firefoxにしか乗ってない)JavaScriptへの互換が不完全とかって難癖つけたりするのは、ほんと良くないですね……。

SourceMap

Web Essentialsの吐いてくれるSourceMapとは何ぞや、というと、これはTypeScriptのままデバッグができます。コンパイル時にJSを吐いてくれる系言語の欠点として、デバッガを使ったデバッグが困難、というのが挙げられますがSourceMapを使うとそれも解決、します。

現状、対応ブラウザはChromeと、まあ、他は知らないのですが、とりあえずChromeは対応しています。IE10(とVS2012内蔵デバッガ)も対応してくれると嬉しいなあ。Chromeのデバッガの不満点としては、ブレークポイントが行単位でしか貼れないことですね。ラムダ式の内側に貼れないと、特にLINQのような一行ラムダを多用するものではデバッグがとても不便でして。この辺、改善されていってくれると嬉しい話。

vs JavaScript(のIntelliSense)

実は、VisualStudio 2012のJavaScriptはかなりサポートが手厚く、裏で常にコードを実行して補完候補を出してくれたりします。

なので、純粋なIntelliSenseの効きだけでいうと、TypeScriptはJavaScriptに負けているかもしれない!如何せん、特にlinq.jsではシーケンスの要素がanyになってしまいますからね。JavaScript(を裏で動かして解釈する)ならば、ここも補完効いてしまうという。最近のJavaScript IDEは進化しすぎで恐ろしい……。

ジェネリクス

仕様書にも明言されていますが、正式リリースまでには搭載する予定があるそうです(ちなみに現在は0.8)。ジェネリクスが乗っかるとlinq.jsがすっごくパワフルになるんですよ。如何せん、今はシーケンスの要素の型が全てany扱いで補完が全く効かなくてTypeSafeでもなんでもないのですが、ここが型付けされると完璧なIntelliSense生活!C#並というかむしろC#超えるぐらいの勢いでパーフェクトなLINQ to Objects!なので、相当に待ち遠しいです。

Compiler as a Serviceの未来

TypeScriptのコンパイラはTypeScriptで書かれてます。これ、別にかっこつけとかでもなんでもなく、非常に重要な意味を持ちます。で、いきなり分かりやすく成果物として出してくれているのがTypeScript Playground。構文解析がJavaScriptで可能だから、Web上で全て完結するIDEが作れる。C#も次のバージョンではC#コンパイラがC#で書かれるという計画があります。そのことがもたらす価値の一部分は、TypeScriptが教えてくれます。いや、むしろブラウザ上で全て完結というのは、C#以上の魅力がありますね、正直……。

結論

TypeScriptは、良い言語だと本当に本当に思います。私は、素のJavaScriptも別にそこまで嫌いではないのですけれど、やっぱ、違うなあ、と。なので今後は積極的に使っていきたいところです(CSSもLESSで!)。

言語設計者が同じということもありますが、特にC#erには絶対馴染むと思うので、(linq.jsとセットで)今までJavaScriptとは無縁だった人も手を出して欲しいですね。きっと気に入りますし、視点が変わります。勿論、ネイティブJSerも是非是非触ってみるといいと思います!というか触ってほしいです。

あ、あと、軽く流しましたがVisual StudioユーザーならWeb Essentialsも必ず入れておきましょう。これがあるのとないのとでは、TypeScriptの使い勝手全然違ってくるので、TypeScript試すならば必須です。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive