ltxml.js - LINQ to XML for JavaScript

以前、linq.js ver.3のセッションの時に、ちょびっとだけ触れたLINQ to XML for JavaScriptが公開されました!

作者は私ではなく、Eric White氏です。誰?ってことですが、元Microsoftの人です。氏のMS時代のMSDNブログのLINQ to XMLタグには超DEEPなLINQ to XMLの使いこなしが書いてあって必見。というわけで、非常にLINQ to XMLに詳しい、というか、MSDNのLINQ to XMLの解説ドキュメントを書いたのは氏だそうです。なので、詳しいとかそういう次元じゃなく、普通に中の人ということですね。

概要

そんなわけで、ltxml.jsとは、C#のXML操作ライブラリであるLINQ to XMLのJavaScript移植です。C#のLINQ to XMLがLINQ to Objectsの上に構築されている(ElementsやDescendantsの戻り値がIEnumerableとなり、LINQベースのクエリ操作となる)ように、ltxml.jsはLINQ to ObjectsのJavaScript移植であるlinq.jsの上に構築されています。ltxml.jsのelementsやdescendantsは、linq.jsのwhereやselectなどのクエリ操作によってXMLを展開できます。

C#版と構造はほとんど一緒です。ただし、JavaScriptの慣習に則りメソッド名がlowerCamelCaseであることと、プロパティが.getHoge()であること(ただしECMAScript 5に対応しているならば.hogeといったようにプロパティでアクセスできます)、オペレーターオーバーロードが存在しないことによる挙動の違い程度です。また、C#版よりも機能向上している面もあります。それは、私がlinq.jsにおいてC#のLINQ to Objectsで物足りないと思った機能を追加していたようなもの、でしょうか、多分ね、きっと。

また、パフォーマンス上の考慮により、descendantsなどは、デフォルトは即時実行で配列(をEnumerableでラップしたもの)を返します。.lazy = trueをXElementなどに投下することで、完全な遅延実行になります。もし巨大なXMLを扱うのならば、遅延実行が効果を発揮するでしょう。通常考えられるサイズのXMLならば、デフォルトのとおり即時実行のほうが良好だと思われます。

使い方

ぶっきらぼうにも、ドキュメントがほとんどないですね!まあ、それは追々紹介されていくことでしょう。ともあれ現状は、ファイルをダウンロードするとltxmlTest.htmlというファイルがあって、それがユニットテスト兼サンプルになっているので、とりあえずそれを読みましょう。また、JavaScript特有の違いはあるものの、基本的にはC#のそれと等しいので、MSDNのLINQ to XMLの解説ドキュメントがまんま使えないこともないです。

ともあれ、まずは簡単なXMLをパースしてみましょう。

var xml =
    "<Contacts>\
        <Contact>\
            <Name>Peter Hage</Name>\
            <Phone>206-555-0144</Phone>\
        </Contact>\
        <Contact>\
            <Name>John Hoge</Name>\
            <Phone>106-333-2222</Phone>\
        </Contact>\
        </Contacts>";

// parseでただの文字列からLINQ to XMLのXElementに変換
var xElem = Ltxml.XElement.parse(xml);

// 子孫ノードのNameを選択し、値だけ抽出
var names = xElem.descendants("Name")
    .select(function (x) { return x.getValue(); })
    .toArray();

alert(names); // Peter Hage, John Hoge

descendants.selectといったように、LINQです!完全に!これをLINQと言わずして何をLINQと言うか!

名前空間

ltxml.jsの全てのクラスはLtxmlオブジェクトの下に格納されています。グローバルを汚さない。しかし、いちいちLtxml.XElementなどと呼ぶのは面倒くさい話です。以下のようなショートカットを先頭に用意するのをお薦めします。

var XAttribute = Ltxml.XAttribute;
var XCData = Ltxml.XCData;
var XComment = Ltxml.XComment;
var XContainer = Ltxml.XContainer;
var XDeclaration = Ltxml.XDeclaration;
var XDocument = Ltxml.XDocument;
var XElement = Ltxml.XElement;
var XName = Ltxml.XName;
var XNamespace = Ltxml.XNamespace;
var XNode = Ltxml.XNode;
var XObject = Ltxml.XObject;
var XProcessingInstruction = Ltxml.XProcessingInstruction;
var XText = Ltxml.XText;
var XEntity = Ltxml.XEntity;
var XEnumerable = Ltxml.XEnumerable;

また、C#版ではEnumerableへの拡張メソッドとして用意されていた幾つかのメソッドは、ltxml.jsではEnumerableに追加されているasXEnumerableを呼び、XEnumerableへと変換することで、呼び出すことができます。しかし、もしそれを手間だと思う場合は、linq.jsのEnumerableを拡張することで、よりスムーズに接続することが可能です。ただし、C#版ではジェネリックによって区別されていましたが、JavaScriptではジェネリックが存在しないので、汎用性のないシーケンスの要素がltxml.jsに固有でなければならないメソッドをEnumerableに追加することとなります。また、removeなどは、他の人の拡張と名前が衝突する可能性が高いことなどにも注意。

Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.ancestors = Ltxml.XEnumerable.prototype.ancestors;
Enumerable.prototype.ancestorsAndSelf = Ltxml.XEnumerable.prototype.ancestorsAndSelf;
Enumerable.prototype.attributes = Ltxml.XEnumerable.prototype.attributes;
Enumerable.prototype.descendantNodes = Ltxml.XEnumerable.prototype.descendantNodes;
Enumerable.prototype.descendantNodesAndSelf = Ltxml.XEnumerable.prototype.descendantNodesAndSelf;
Enumerable.prototype.descendants = Ltxml.XEnumerable.prototype.descendants;
Enumerable.prototype.descendantsAndSelf = Ltxml.XEnumerable.prototype.descendantsAndSelf;
Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements;
Enumerable.prototype.nodes = Ltxml.XEnumerable.prototype.nodes;
Enumerable.prototype.remove = Ltxml.XEnumerable.prototype.remove;

私個人としては、Enumerableへの拡張はそんなに薦められないかな、という感じですが、ヘヴィにXMLを処理する局面では、拡張したほうがサクサク書けて良いのではかとも思います。この辺は好みでどうぞ。

関数型構築

XMLを作るときは、コンストラクタで可変長引数として連鎖させます。これをLINQ to XMLでは関数型構築と呼んでいます。

var xml =
    new XElement("root",
        new XElement("user", new XAttribute("id", 1),
            new XElement("age", 100)));

// <root><user id = '1'><age>100</age></user></root>
alert(xml.toString()); // toStringで文字列化

閉じタグが不要であったり、安全であったり(JavaScriptだってカッコ閉じ忘れとかは警告入るからね)と、生文字列で組み立てるのに比べて、遥かに利点があります。また、要素がlinq.jsのEnumerableである場合も、きちんと展開されます。

// C#と同様にEnumerable<XElement>は展開される
var users = Enumerable.range(1, 10)
    .select(function (x) {
        return new XElement("user", new XAttribute("id", x),
                   new XElement("age", x * x));
    });

var xml = new XElement("root", users);

// <root>
//   <user id = '1'>
//     <age>1</age>
//   </user>
//   <user id = '2'>
//     <age>4</age>
//   </user>
//   (略)
// </root>
alert(xml.toString(true)); // 引数にtrueを渡すとインデントつけて整形

どうでしょう、イメージつきます?

用途

Eric White氏がOpenXML(Officeのxlsxとかがそれ)の専門家ということで、JavaScript上でOfficeファイルを容易に扱うことが可能になるでしょう。つまり、サーバーサイドでのコンバート処理など不要に、JavaScriptだけでビューワーであたり要素抽出であったりが、完結する未来があります。なんて興奮する話でしょう!

とはいえ、それはあまりにも専門的すぎて、実に面白いし役立つでしょうけれど、実際にそれでもって作り上げる側に周るのは極少数の人に違いありません。では、他にXMLを使う局面なんてあるのか?ふむ……。恐らく、ブラウザ上で動くアプリケーションにとって機会はないでしょう、どこもかしこもJSONに集約される!AJAXのXはXMLのX!だった時もありました。いや、今もそうでしょうけれど。

では、どこに?というと、Node.jsはどうだろう?結局、未だにXMLのサービスなどは多いのだ。RSSはJSONにならないでしょう。サーバーサイドで行うならXMLは無視できないのだ。またはクライアントアプリでも、TitaniumやPhoneGapはどうだ?またはWindows 8のアプリケーションはJavaScriptで作ることができる。そこではまだまだXMLは現役に違いない。JavaScriptの活躍範囲がブラウザを超えていけばいくほど、残り続けるXMLに遭遇する機会は増える。

AtomPub(ああ!今はもうあまり名前を聞かない!)の構築に、LINQ to XMLの関数型構築は役に立つことでしょう。とにかく言えることは、XMLは決して死んでいないし、望まれるか望まれないかはともかくとして、生き残り続けるでしょう。そのために、私達には強力な武器が必要なのです、LINQ to XMLという。

もしくは、単純にHTMLビルダーとして使ったっていいかもしれない。HTMLはXMLなのだもの、ええ、大丈夫、そのようにも使えます。文字列連結してHTMLを組み立てるなんてしなくていい。また、もちろん、HTMLのDOM操作にだって、ね(でもDOMの操作ならば、きっとjQuery使いますね)

未来

ところでしかし現状ltxml.jsはベータです。何故か。linq.jsがまだベータだからです。ver.3.0.3-Beta4ってなんだよクソが。すみませんすみません、なるべく早く正式リリース出来るよう鋭意努力します。NEETなので暇、じゃあないんですよねえ、残念ながら。でも急ぎます。さすがに!いやほんと私の作業ペースの遅さには我ながらどうかと思う感じですが、もうさすがに猶予ないですね!

ちなみに7月31日に作ってるよー、ってメール貰って、そこから何度かやり取りしてました。ltxml.jsのコード自体、かなりパワフルにlinq.jsを使いこなしているので(私のアドバイスの賜物です!)そういう意味でも面白いですね。ちなみに、その時には8月中にlinq.jsリリースするって言ってたかなあ、今もう10月末ですねえ、どうなってるんでしょうねえ、ごめんなさいごめんなさい。

既存JavaScriptをTypeScriptとして修正する方法

JavaScriptはTypeScriptです。ほぼほぼ。.jsを.tsとして変更すれば動きます。というほど世の中甘くなくて、まあ、大抵は動きません。えー、なにそれ、欠陥品じゃないの?と思われるかもですが、いえ、結構単純な話です。例えばですが

var x = 0;
x = "hogehoge";

このコード、JavaScriptとしては正しいですが、TypeScriptとしては間違っていてコンパイル通りません。xがnumberとして推論されるので、"hogehoge"が代入できないからです。じゃあどうするの?というと、

var x: any = 0;
x = "hogehoge";

anyとして型定義してやればいいんですね。もしくは

var x = <any>0;
x = "hogehoge";

でもいいですが。<>はキャストみたいなものです。ちなみに、こういったことの実例はTypeScriptのソースをダウンロードしてきて、\src\harness\external\json2.ts に、json2.jsをtsに変換した例が見れます。ほんの2, 3箇所anyの注釈を入れているだけで、ほぼほぼそのままですね。実際のところ、↑みたいなゆるふわキャストなんて、たとえJSといえど多用してるわけがないので、作業的な手間はあまりありません。やることは、コンパイルエラーの出た箇所をポチポチとモグラたたきするだけなので、楽ちん。

実際にやってみる

理屈上はそうですが、実際やってみるとどうなんでしょうねえ、ということで、linq.jsでやってみました。(なお、linq.jsの型定義自体はlinq.jsのTypeScript対応とTypeScript雑感で手付けですでにやってあります)。まず.tsにしてコンパイルかけてみると、赤波線が全体に出てきてわけわからんオワタ!エラー90件!

で、まあ、こういう場合は問題は基底部分にあります。

(function (root, undefined) {
// 中略
})(this);

問題なのはundefinedです。function(root, undefined) として定義しているのに、(this)ということで、呼んでないから。冷静に見てみれば、ただたんにメソッド呼ぶ引数が足りないぜ、って言ってるだけですな。ちなみにこのコード自体は、undefinedは代入可能な代物で破壊されている可能性があるから(あるわけないけど!)、安全なundefinedを作ろう、という古臭いイディオムです。

エラーが90件もあってわけわかりませんが、一番最初のエラーが「Supplied parameters do not match any signature of call target」なので、やっぱり冷静に見てみれば、ちゃんと教えてくれていた、と。TypeScript優しいのね。

なのでundefinedを抜けば真っ赤っ赤はなくなります。OK。だがまだエラーは続く。というかエラー件数は89件になっただけである。

お次はEnumeratorがないぞ!というエラー。

if (typeof Enumerator !== Types.Undefined) {
if (typeof Windows === Types.Object && typeof obj.first === Types.Function) {

このEnumeratorはIEのみに存在するオブジェクトで、Windows Script Hostで列挙するのに使ったり使わなかったりする、今では知らない人のほうが遥かに多いであろう謎オブジェクトです。Windowsのほうも同様に、Windows8用アプリケーションにしか存在しません。さて、これへの対処は、定義ファイルのない外部ライブラリを使う際と同じで、anyでdeclareします。ファイルの先頭に

declare var Enumerator;
declare var Windows

と書いておけばOK。しかしまだまだエラーは続くよ!該当箇所はここ。

var Enumerable = function (getEnumerator) {
    this.getEnumerator = getEnumerator;
};

// このUtilsで赤線
Enumerable.Utils = {}; // container

このEnumerableが意図するところはコンストラクタです。new Enumerable()するためのものです。で、JavaScriptでは関数にもオブジェクトを生やせますが、TypeScriptでは生やせません。対処方法はまあ、面倒くさいのでEnumerableをanyにしましょう。

var Enumerable: any = function (getEnumerator) {
    this.getEnumerator = getEnumerator;
};

これだけで割と一気に解決します!89件あったエラーが残りほんの数件に!any最強!dynamic! で、linq.jsでは同じようにOrderedEnumerableとArrayEnumerableというものが存在するので、同様にanyにしておきます。

そんなわけで、なんとなくわかったと思いますが、ようするにエラーの出てるところを片っ端からanyにしていくだけです。ただしルート階層に近いものを優先的にany化すると、その下にぶら下がってるものは全部解決するので、意外とそんな手間じゃありません。

あとは一番下にAMD対応もどきのとこがあるのですが、これはそもそも微妙なのでまるごと削除して解決(てきとー)。で、対応はほんとこれだけです。あっという間だし簡単ですなあ。TypeScriptのJavaScriptとの互換性は本物だ!

declarationsオプション

で、ここからが本題であって本題ではないのですが、TypeScriptはtsc -declarationsとオプションをつけてコンパイルすると、d.tsを吐いてくれます。ちゃんと型定義されたtsファイルならちゃんとしあd.tsを吐いてくれます。役立ちです。

で、人間欲が出るもので、もしこれを、↑のように修正した.tsにかませてやるとどうなる?もし、たとえanyであっても定義テンプレを吐いてくれたら、そこから注釈入れてくだけですむわけで、随分と楽になりますよね?面倒くさい型定義よさようなら。

というわけで、こいつをdeclarationsオプションをつけてコンパイルしましょう。

tsc linq.js.ts -declarations

期待のlinq.js.d.tsの結果は

var Enumerator;
var Windows;

になります(笑)。はい、関数で丸ごと括った部分が消滅してしまいました。クソが。今回は定義ファイルが欲しいだけなので、関数で括る部分を除去して再度コンパイルすると

var Enumerator;
var Windows;
var Functions: { Identity: (x: any) => any; True: () => bool; Blank: () => void; };
var Types: { Boolean: string; Number: string; String: string; Object: string; Undefined: string; Function: string; };
var Utils: { createLambda: (expression: any) => any; isIEnumerable: (obj: any) => bool; defineProperty: (target: any,methodName: any,value: any) => void; compare: (a: any,b: any) => number; dispose: (obj: any) => void; };
var State: { Before: number; Running: number; After: number; };
var IEnumerator: (initialize: any,tryGetNext: any,dispose: any) => void;
var Yielder: () => void;
var Enumerable: any;
var OrderedEnumerable: any;
var SortContext: any;
var DisposableEnumerable: (getEnumerator: any,dispose: any) => void;
var ArrayEnumerable: any;
var WhereEnumerable: (source: any,predicate: any) => void;
var WhereSelectEnumerable: (source: any,predicate: any,selector: any) => void;
var Dictionary;
var Lookup: (dictionary: any) => void;
var Grouping: (groupKey: any,elements: any) => void;

外に出したくないもの(Yielderとか)は、まあ、あとで別途削除すればいいんですが、しかしそもそも肝心のEnumerableメソッドが全部出てないぞ! 理由としては、ようするにanyつけちゃったから。うーん、これじゃ実用度ゼロですね。

そもそもfunctionで定義したクラス(をコンストラクタとして使いたい)というのを、それがコンストラクタなのか関数なのかをどうやって区別するんだ?って話ですし、無理ですなー。(ファイル全てをなめてthis.してるのはクラスとか.prototype = hogehogeしてるのはクラスとか、曖昧な判定はできるでしょうけれど、それは危険ですしね)。

夢は見ちゃダメ。でもMicrosoftならきっといつかはやってくれるはず!(実際、GWT用に空気読んでJavaScriptからJavaの定義を吐いてくれるものは存在するとか)

まとめ

プレーンなJavaScriptはほぼほぼTypeScriptになります。素晴らしい互換性です!一方、型定義自動生成のほうは無理でした。地道に頑張りましょう。

あ、そうそう、今回の記事で言いたいのは別に表題通りの話じゃあないです。既存JSは既存JSとして使えばいいので、わざわざTypeScript化する必要なんて全然ありません。いえ、あります、ちゃんと型付けするならば。でも、今回のようにanyを付けて回る程度の話は全くの無意味です。じゃあどうでもいいかといえば、やっぱりそうじゃなくて、TypeScriptとJavaScriptの互換性というのはどういうものなのか、というとこは位置づけ的には大事ですからね、漠然とじゃあなく抑えておきたいところ。

gloopsを退職しました。

今日というか昨日というか、金曜が最終出社日となりました。

今年の1月1日に入社してから10ヶ月。非常に濃密だったのであっという間でしたね。良い経験ができたし、私の方からも十分に貢献できたとは自負しています(LINQの布教とかね!)。とはいえ、まだまだやり残していることは山のようにあり、時期的にも、まさにこれから!というタイミングなので、心残りは非常にあります。gloops自体は非常に良い会社ですし、これからますます技術的にも力強く、面白くなっていくところです。なので、その点は安心してください。ですが、個人的により大きな飛躍を目指したく決断と相成りました。

円満、です。最後に社内勉強会でThe Patterns of LINQというセッションをやりまして、それが置き土産です。そんなこんなで温かく送っていただいて、本当にgloopsの皆様へ感謝!

ちなみに日本にずっといます。いつぞやかにはシンガポールが、とかって話もありましたが、あれは種々諸々で爆散しました。それは凄く残念でしたね……、、海外への挑戦というのは、またいつか機会があればやりたいです。ともあれ今は、当面は地下に潜伏していますが、必ず浮上しますのでしばしお待ちくだしあ。

非同期WebRequestとTimeout処理の今昔

最近はTypeScriptにお熱ですが、とはいえ、C#も大好きな私です。むしろC#は大好きです。今日はすっかり飽き飽きな非同期のTimeout処理について、おさらいすることにしましょう!題材はいつもどーりWebRequestでいいですよね。

まず、都合のいいTimeoutをシミュレートできるAPIは探せば多分あるでしょうが、面倒なので自分で作りましょう。いえ、簡単です。「空のASP.NET WebApplication」を立ち上げてジェネリックハンドラを追加。とりあえずレスポンスを返すのに3秒かかるということにしときましょう。

public class Timeout : HttpTaskAsyncHandler
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        await Task.Delay(TimeSpan.FromSeconds(3)); // 3秒かかるってことにする
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World");
    }
}

で、そのまま実行してIIS Expressで動いてもらってれば準備できあがり。

同期の場合

さて、そしてConsoleApplicationを立ちあげて、まずは同期でやる場合でも見ましょうか。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
req.Timeout = 1000; // 1秒でタイムアウト

req.GetResponse();

これはちゃんとタイムアウトでWebExceptionを返してくれます。そりゃそーだ。

古き良き非同期の場合

じゃあBegin-Endパターンの非同期でやってみましょうか。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
req.Timeout = 1000; // 1秒でタイムアウトのつもり

req.BeginGetResponse(ar =>
{
    var res = req.EndGetResponse(ar);
    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}, null);

Console.ReadLine(); // 適当に待つ

この結果はなんと、普通にHello Worldと表示されてしまいます。はい、Timeout機能していません、全く。そう、WebRequestのTimeoutプロパティによる設定は同期限定なのだよ、なんだってー。このことはMSDNのBeginGetResponseのとこにも書いてあって、「非同期要求の場合は、クライアント側のアプリケーションが独自のタイムアウト機構を実装する必要があります」ということになっています。

ThreadPool.RegisterWaitForSingleObject

なので、そこに書いてあるとおり、ThreadPool.RegisterWaitForSingleObjectで実装してみましょう。

var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
// req.Timeout = 1000; このTimeoutはイミナイのでイラナイ

var result = req.BeginGetResponse(ar =>
{
    var res = req.EndGetResponse(ar);
    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}, null);

ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, (state, timeout) =>
{
    // 引数で指定した時間の後にここの部分が発火する。
    // そのとき非同期処理が完了していなければ(Timeoutしていれば)timeoutがtrue, 普通に終了してればfalse
    if (timeout)
    {
        Console.WriteLine("TIMEOUT!!");

        var _req = (WebRequest)state;
        if (_req != null) _req.Abort(); // あぼーんでキャンセルというか打ち切る
    }
}, req, timeout: TimeSpan.FromSeconds(1), executeOnlyOnce: true);

Console.ReadLine(); // 適当に待つ

うん、ややこしいですね。ウンザリです。しかし昔はこれぐらいしか手段がなかったのだからShoganai!

C# 5.0で救われよう

そんなこんなで一気に時代が進んで、C# 5.0です。GetResponseAsyncですね!GetResponseAsyncなら、GetResponseAsync先生ならやってくれる、と思っていた時がありました。

static async Task Run()
{
    var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
    req.Timeout = 1000; // ま、このTimeoutはイミナイですよ

    var res = await req.GetResponseAsync();

    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}

static void Main(string[] args)
{
    Run().Wait();
}

結果はBegin-Endの時と同じでTimeout指定は無視されます。はい残念残念。所詮別にBegin-Endと何も変わってはいないわけです。とはいえ、Taskならば、この辺、柔軟に処理を仕込めます。

Timeoutという拡張メソッドを作る

async/awaitやTaskといった道具立てはあるのですが、細かい色々なものはない(Cancelを足すとかTimeoutを足すとかRetryを足すとか、この辺はよく使うであろうシチュエーションだと思うので、自分の道具箱に仕込んでおくと幸せになれます)ので、作りましょう。

public static async Task Timeout(this Task task, TimeSpan timeout)
{
    var delay = Task.Delay(timeout);
    if (await Task.WhenAny(task, delay) == delay)
    {
        throw new TimeoutException();
    }
}

public static async Task<T> Timeout<T>(this Task<T> task, TimeSpan timeout)
{
    await ((Task)task).Timeout(timeout);
    return await task;
}

単純ですね。ポイントはTask.WhenAnyで、これは特殊なやり方ではなくて、イディオムです。C# 5.0を使っていくなら覚えておきましょう、絶対に。

さて、これを使えば

static async Task Run()
{
    var req = WebRequest.Create("http://localhost:18018/Timeout.ashx");
    var res = await req.GetResponseAsync().Timeout(TimeSpan.FromSeconds(1));

    Console.WriteLine(new StreamReader(res.GetResponseStream()).ReadLine());
}

static void Main(string[] args)
{
    Run().Wait();
}

超シンプルになりました。やったね!

HttpClient

ちなみに.NET 4.5から入ったHttpClientは、非同期操作しか提供していない、だけに、ちゃんとTimeoutプロパティが非同期でも対応していますので、フツーはこっちを使うと良いでしょう。

static async Task Run()
{
    var client = new System.Net.Http.HttpClient() { Timeout = TimeSpan.FromSeconds(1) };
    var s = await client.GetStringAsync("http://localhost:18018/Timeout.ashx");

    Console.WriteLine(s);
}

static void Main(string[] args)
{
    Run().Wait();
}

吐いてくる例外はSystem.Threading.Tasks.TaskCanceledExceptionです。これは、中でCancellationTokenSource.CreateLinkedTokenSourceとリンクさせた上で、CancellationTokenSourceのCancelAfterによってTimeoutを処理しているからです。HttpClientも、最終的にネットワークとやり取りしている部分はWebRequestですから。

まとめ

まあ、今までは細かい罠があってクソが、となる局面も少なからず多かったわけですが、ようやく整理された、感があります。しょっぱいことは考えないで、甘受していきたいですねー。

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