Archive - linq.js

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月末ですねえ、どうなってるんでしょうねえ、ごめんなさいごめんなさい。

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試すならば必須です。

linq.js ver.3.0.2-RC, WinRT対応、最新RxJS対応など

RCリリースしました!これでAPI弄りは終了で、あとはドキュメント周りの調整のみといったところです。

ダウンロードはダウンロードボタンからではなく、ダウンロードタブからlinq.js ver.3.0.2-RCを選択してください。というかここからダイレクトに飛んでください

Beta2から結構立ちましたが、その間に、ノートPCがぶっ壊れたり(今もサポートで修理中、ちょうどうっかり未Pushなコミットが溜まってた状態で逝ってしまったのが痛手でどぅーにもこーにも)、そもそも日本にいなかったり(シンガポールにいましたというか、今は日本にいますが、これからは基本的にシンガポール中心になる感)とか、まぁ色々で色々で捗らずで。

さて、その間でもないですが、プログラミング生放送で8/25にセッションを持ちまして、そこでlinq.js ver.3の紹介をしましたので、スライド・録画ともどもにどうぞ。

linq.js ver.3 and JavaScript in Visual Studio 2012 from neuecc

Visual Studio 2012はJavaScript関係がハイパー強化されているのですけれど、そのビッグウェーブにフルに乗っかって強力なんだぞ!みたいな。そういったVS2012のパワーとかの部分は、デモの比率が高いので、時間に余裕があれば、是非とも録画を見ていただけると嬉しいです。

あと、こそっとLINQ to XMLのアナウンスを、こそっと、ね。ふふり。

あ、そうだ、スライドに関しては一点、嘘があります。VS2012にはjQueryのIntelliSenseドキュメントの日本語版は入っていません。英語のみです。本当にごめんなさい、これ、確認していなくて、VS2010では日本語訳されたのが入ってたからそうなんだろうなあ、とか思ってたのですが、そんなことはなかったです。予算的な都合なのでしょうか……?ともあれ、申し訳ありませんでした。

更新事項

今回も破壊的変更が入っていて、firstOrDefault, lastOrDefault, singleOrDefaultの引数が変わりました。

// 以前
.firstOrDefault(defaultValue, [predicate]);
 
// これから
.firstOrDefault([predicate], [defaultValue]);

です。ようするに引数が逆になりました。predicateが先にあるのがC#のLINQと同じ並び順なわけで、ここだけ、何故かlinq.jsはC#に従ってなかったのですね。理由としてはJavaScriptにはdefault(T)は存在しないのでdefaultValueの指定を強制するために、第一引数に持ってきてやらなければならない。と、当時、3年前は思ってたらしーんですが、別に普通にないならないでnullでいいだろ馬鹿が、むしろ引数がこれだけ違うとか紛らわしいだろクソが。ということにやっと至りまして、変えられるのは今しかない!ということで変えました。

コードスニペット

そういえば3.0.1-Beta2のリリース時にはブログ書いてませんでしたが、そこでコードスニペットを改良したのを入れました。linq.js ver.2に同梱してたものよりずっと良くなってるのでリプレースするといいです。非常に捗ります。というか、もうこれなしで書く気しないぐらいに。

RxJS

Reactive Extensions for JavaScript(RxJS)がオープンソースになりました、完全にソースコード公開です、ぱちぱちぱち。今まではScriptSharpで変換してたような気配だったのですが、完全手書きに移行したようです。

それに伴ってlinq.jsのRxJS連携も、若干手を加えました。ところで、今のところNuGetにあがっているものは、GitHubの最新に追随してません。古いままです。なので、NuGet版だとlinq.jsのRxJS連携は動かなかったりします(ビミョーに中身変わってるんですよ、いやはや……)

今のところ新RxJSに関してはリリースとかも打たれていないので、ステータスがどうなっているのか、よくわかりません。まあ、近日中に、かなあ?どうなのでしょうね。とりあえず、動向には注目、ということで。

WinMD

Windows 8のアプリケーション(Metroとは言えなくなりました!)はJavaScriptでも開発できるわけでして&C++やC#で作成されたライブラリも条件付きというか専用のコンポーネントとして作れば、JavaScriptでも読み込むことができます。 コレクション周り、IList<T>はJavaScriptでは配列として扱えます。なので、これは何もなく普通に列挙できるし、今までのlinq.jsでも扱うことができました。しかし、IEnumerable<T>はIIterable<T>というものに化け、これは独特の列挙の仕方を要求するため、フツーには扱いづらく、また、今までのlinq.jsでも使えませんでした。

が、ver.3.0.2-RCからは、IIterable<T>対応を入れたので、列挙可能です!

// IIterable<T>を列挙できるのはlinq.jsだけ!
var iterable = ToaruLib.GetIterable();
Enumerable.from(iterable).forEach();

WSH(JScript)対応といい、Windows固有のニッチ需要に100%応えるのはlinq.jsだけ。いやほんと。

文字列ラムダと無名関数

これは今までもの話なのですが、文字列ラムダに抵抗あるー、というのは分かります。しかし、無名関数を渡すこともできますぜ、というのは、分かって欲しいというか、利用シーンとしては半々なイメージなんですよね。例えばですが二つのJSONをJoinするのに

var jsArrayA = [{ "projectid": 122, "projecttype": "radio" },{ "projectid": 133, "projecttype": "tv" }];
 
var jsArrayB = [ { "actionid": 1, "name": "kuow", "pid": 122 }, { "actionid": 2, "name": "kplu", "pid": 122 }, { "actionid": 3, "name": "abc", "pid": 133 }, { "actionid": 4, "name": "espn", "pid": 133 } ];
 
var queryResult = Enumerable.from(jsArrayA)
    .join(jsArrayB, "$.projectid", "$.pid", function (a, b) {
        return {
            projectid: a.projectid,
            projecttype: a.projecttype,
            actionid: b.actionid,
            name: b.name,
            pid: b.pid
        }
    })
    .toArray();

これ、全部、無名関数で書くことも可能です。”$.projectid”をfunction(x){ return x.projectid} と書けばいいので。”$.pid”の部分もそうです。でも、それってすごくだるいですよね。

LINQはプロパティ名を指定するだけの無名関数を要求するシーンが多いです。どうせ、JavaScriptは動的言語、コンパイルチェックも働かないのですから、文字列で指定しても一緒でしょう。また、これは、jQueryのセレクターと同じようなものです。そう考えれば、文字列指定にもさして抵抗感はないのではないでしょうか?短くサラッと文字列でプロパティ名を指定したほうが、書きやすいし可読性も高いです。

同様に、最後のJOIN結果を新しいオブジェクトに変換しているところは、文字列ラムダで書くことも可能です。”{projectid:$.projectid, projecttype:$.projecttype,….}”といったように。でも、それって今度は逆にとても見づらくて可読性落ちますよね。長いコード、入り組んだコードになるようならば、素直に無名関数を使ってもらうのがいいな、と思っています。

次回

次は正式リリースです!いつになるかは、いつになるかしらん!8月末が正式リリースのつもりだったのに、一か月遅れでRCリリースですからねえ、んもぅー。ともあれ、間違いなく良い出来になっているので、楽しみにしてください。で、もうその前にRCじゃんじゃん使ってくだしあ。

linq.js ver.3.0.0-beta, メソッドlowerCamelCase化、など

ずっとやるやる詐欺だったlinq.js ver.3ですが、ようやく始まりました!

トップページのボタンはstableのものになるので、DOWNLOADSタブからver.3.0.0-betaを選んでください。また、NuGetを使っている人はInstall-Package linq.js -Preで入ります。他にlinq.js-jQuery -Pre, linq.js-RxJS -Pre, linq.js-QUnit -Preなどり。

lowerCamelCase化

はい。ようやくJavaScriptらしくなりました。UpperCamelCaseにはC#っぽいとか、キーワードで衝突しなくなるとか、ちょっとした利点はなくもないのですが、そもそも.NETっぽくないほうがいい、というかJavaScriptの世界にちゃんと馴染ませたいという思いのほうが強い。そして、.NETの人以外にも使って欲しくて。

Enumerable.range(1, 10)
    .where(function(x){ return x % 2 == 0})
    .select(function(x){ return x * x});

当然ながら超破壊的変更です。ver.2までのコードは一切動かなくなります。やりすぎですが、しょうがない。痛くてしょうがないけれどしょうがない。さて、ならばとついでにメソッド名の見直しもしました。

Return -> make
CascadeBreadthFirst -> traverseBreadthFirst
CascadeDepthFirst -> traverseDepthFirst
BufferWithCount -> buffer
ToString -> toJoinedString
Do -> doAction
Let -> letBind
MemoizeAll -> memoize
Catch -> catchError
Finally -> finallyAction
ToJSON -> toJSONString

これを機に、というかこういう機会じゃないとやれないですから。toStringやtoJSONは、上書きしてしまうとマズいので別名にしています。toStringは、まあそのままなので分かると思うのですが、toJSONのほうは、JSON.stringifyで特別扱いされるメソッドなので、こっそり注意が必要なんですね、というか実際ハマッて気づくのに時間かかりました。

extendTo

prototype.js以降、prototype拡張は悪、でしたが、最近のJavaScriptはfor inで列挙しない形での拡張(Object.definePropertyでenumerable:falseにする)が可能になっています。それを全面に押し出したSugarといったライブラリもあり、確かに便利なのですよね。

さて、linq.jsでは配列などをLINQで扱うためにEnumerable.fromで包んでやる必要があって面倒でしたが、配列からそのままselectとかwhereとかが生えていれば、便利、ですよね?なので、任意に拡張できるようにしました。

// Arrayを拡張する
Enumerable.Utils.extendTo(Array);
 
[1, 3, 10, 1000].where("$%2==0").select("$*$");

Enumerable.Utils.extendToを一度呼べば、from不要で直接LINQのメソッドを呼ぶことができます。もしブラウザがObject.definePropertyに対応していなければ、その時はprototypeを直接拡張しています。

さて、LINQのメソッド名とネイティブのメソッド名が被っている場合は、ネイティブのメソッド名を優先して、上書きはしません。例えばjoinとか、concatとか。その場合はByLinqがメソッド名の末尾につきます。joinByLinq、など。

// 名称が被るものはByLinqというプリフィックスがつく
[1, 3, 10].reverseByLinq();
 
// もしくはasEnumerableメソッドを呼んであげればLINQのメソッドのみになります
[1, 10, 100].asEnumerable().forEach(function(x, index){alert(x + ":" + index)});

forEachなどは古いブラウザではそのまま、新しいブラウザではforEachByLinqになる、といったようにブラウザ互換性がなくなるので、個人的にはByLinqの形で呼ぶよりかは、asEnumerableを使うことのほうをお薦めします。

Visual Studio 2012でのIntelliSense超拡張

VS2012でlinq.jsを使うと、ただでさえ充実していたIntelliSenseが更に超補完されます。どのぐらい補完されるか、というと、selector関数でオブジェクトの候補が並んでしまうぐらいに。

もはや完全にC#。あまりの快適さにチビる。勿論、↑の図ではFooは文字列なので、x.Foo.で文字列のメソッド候補がIntelliSenseに並びます。動的言語とは思えない超補完っぷりがヤバい。そして入力補完が最大限に活きるように設計されているLINQなので、組み合わさった時の快適度は半端ない。

Chaining Assertion for QUnit

ユニットテストを書く際に、equal(actual, expected)と書くのが嫌いでした。どちらがactualなのかexpectedなのか一瞬悩むし、そもそも外側から包むのがかったるくて。かといってshouldといった、英語的表現なのも冗長なだけで全く良いとは思っていませんでした。そこでC#ではChaining Assertionといった、actual.Is(expected)でアサートが書けるライブラリを作ったのですが、それをJavaScript用に移植しました。

// 流れるように.isと打ち込むだけ
Math.pow(10, 2).is(100); // strictEqual(Math.pow(10, 2), 100)
 
// コレクションに対する適用は可変長にカンマ区切りで値を並べるだけ。勿論、配列にも使えます。
Enumerable.rangeTo(10, 15, 2).is(10, 12, 14); // deepEqual(Enumerable.rangeTo(10, 15, 2).toArray(), [10, 12, 14])
 
// LINQと組み合わさることでコレクション系のチェックが遥かに容易になる!
[1, 5, 10].all("$<12").isTrue(); // collection assertion with linq.js!

といった感じに書けて、超楽ちんです。使うにはlinq.qunit.jsを別途読み込んでください。

その他

createEnumerable, createEnumerator, createLambdaといった、自作Enumerableメソッドを作るための道具を外部公開するようにしました。是非作っちゃってください。

Enumerable.Utils.createLambda
Enumerable.Utils.createEnumerable
Enumerable.Utils.createEnumerator

更に、メソッドも追加されています。

Enumerable.defer
asEnumerable
merge
choose
isEmpty
distinctUntilChanged
weightedSample
log

それらの細かい使い方などは追々書いていきます。また、merge, zip, concatは複数のシーケンスを引数に受け取れるようになりました。

そして、C#では、以前にneue cc - LINQのWhereやSelect連打のパフォーマンス最適化についてという記事を書いた通り、Where連打やSelect連打、それにWhere->Selectといったよくあるパターンに対して最適化が入っているのですが、それをlinq.jsでも再現しました。なので、Where連打などによるパフォーマンス劣化が抑えられています。また、頻出パターンのWhere->Selectで高速化されたのはかなり大きいと思っています。

それに加えてrange, rangeDown, rangeToといったよく使う生成関数の速度を大幅に上げました(以前はtoInfinity().take()で生成していたのを、独自生成に変更したため)。

なので全体的にパフォーマンスも向上しています。

それと最後に、jQueryのプラグインとしてのものは今回からやめました。なんか混乱するし意味ないな、と思ったので、jQueryとの連携はlinq.jquery.jsによるtoEnumerable/tojQueryを追加するファイルのみとなっています。RxJSに関しても最新版のRxJSと連携できるようにしました(linq.rx.js)

今後

VS2012に対するIntelliSenseの充実化がまだ1/5ぐらいしか出来ていないので、それの充実が優先です。あと、リファレンスやサンプルが書けてないので追加。それらが出来たら、いったんver.3として正式公開します。プログラミング生放送勉強会 第17回@品川 #pronama : ATNDで話すつもりなので、その日、8/25までには正式公開を目指します!というわけで是非是非聞きに来てください。

あ、あとnode.js用にnpm公開も、ですね。

linq.js LT資料

LTで簡単にlinq.jsの紹介をしましたので、その資料を。といっても、資料は全く使わないでLTの場では完全にデモ一本にしました。ええ、こういう場では、やっぱデモ優先のほうがいいかなー、と。資料は資料で、要素がきっちりまとまって紹介という感じなので、見てもらえればと思います。

スライドのテンプレは同じの使っていてそろそろ飽きたので、新しいのに変えたいところ。基本的にはテンプレのテーマまんまですが、やっぱ細かいところでスライドマスタの調整は必要なので、面倒くさー、と思ってしまい中々に気力が。むしろデザイン変更は一年に一回でいいかしらいいかしら?

そういえばどうでもよくないのですが、SlideshareをBlogに埋め込む時はlargeサイズを選んで欲しい。文字潰れてしまうもの、わざわざ小さいサイズで埋め込む必要はどこにもなくて。

文字列を先頭から見て同じところまで除去をlinq.jsとC#で解いてみた

JavaScript で「文字列を先頭から見て同じところまで除去」をやってみました。という記事を見て、「linq.js を使いたかったのですが使いどころがパッと思い浮かびませんでした」とのことなので、linq.js - LINQ for JavaScriptで答えてみます。お題の元はお題:文字列を先頭から見て同じところまで除去からです。解き方も色々あると思いますが、最長の一致する文字を見つけて、それを元に文字列を削除していく、という方法を取ることにしました。

function dropStartsSame(array)
{
    var seq = Enumerable.From(array);
    return Enumerable.From(seq.First())
        .Scan("$+$$")
        .TakeWhile(function (x) { return seq.All(function (y) { return y.indexOf(x) == 0 }) })
        .Insert(0, [""]) // 一つもマッチしなかった場合のため
        .TakeFromLast(1)
        .SelectMany(function (x) { return seq.Select(function (y) { return y.substring(x.length) }) });
}
 
dropStartsSame(["abcdef", "abc123"]).WriteLine();
dropStartsSame(["あいうえお", "あいさんさん", "あいどる"]).WriteLine();
dropStartsSame(["12345", "67890", "12abc"]).WriteLine();

はい、ワンライナーで書けました、って何だか意味不明ですね!まず、例えば”abcdef”から[”a”,”ab”,”abc”,”abcd”,”abcde”,”abcdef”]を作ります。これはものすごく簡単で、Scanを使うだけです。

// ["a","ab","abc","abcd","abcde","abcdef"]
Enumerable.From("abcdef").Scan("$+$$")

素晴らしい!そうして比較のタネができたら、あとは全てのindexOfが0(先頭に一致する)の間だけ取得(TakeWhile)します。[”abcdef”,”abc123″]だとシーケンスは[”a”,”ab”,”abc”]に絞られます。必要なのは最長のもの一つだけなのでTakeFromLast(1)で最後のものだけを取得。もし一つもマッチしなかった場合は代わりに”"が通るようにInsertで事前に先頭にさしてやってます。あとは、その”abc”を元にして文字列を置換したシーケンスを返してやるようにすればいい、というわけです、はい。

少し修正

SelectManyで繋げるのは悪趣味なので、ちょっと変えましょう。

function dropStartsSame(array)
{
    var seq = Enumerable.From(array);
    var pre = Enumerable.From(seq.First())
        .Scan("$+$$")
        .TakeWhile(function (x) { return seq.All(function (y) { return y.indexOf(x) == 0 }) })
        .LastOrDefault("");
 
    return seq.Select(function (x) { return x.substring(pre.length) });
}

変数を一つ置いてやるだけで随分とすっきり。無理に全部繋げるのはよくないね、という当たり前の話でした。

C# + Ix

C#とIxで書くとこうなるかな?基本的には同じです。(Ixって何?という人はneue cc - LINQ to Objects & Interactive Extensions & linq.js 全メソッド概説を参照ください)

static IEnumerable<string> DropStartsSame(params string[] args)
{
    var pre = args.First()
        .Scan("", (x, y) => x + y)
        .TakeWhile(x => args.All(y => y.StartsWith(x)))
        .LastOrDefault() ?? "";
    return args.Select(x => x.Substring(pre.Length));
}
 
static void Main()
{
    var x = DropStartsSame("abcdef", "abc123").SequenceEqual(new[] { "def", "123" });
    var y = DropStartsSame("あいうえお", "あいさんさん", "あいどる").SequenceEqual(new[] { "うえお", "さんさん", "どる" });
    var z = DropStartsSame("12345", "67890", "12abc").SequenceEqual(new[] { "12345", "67890", "12abc" });
 
    Console.WriteLine(x == y == z == true);
}

Ixで使ってるのはScanだけですけれど。

Deferの使い道

ところで、上のコードは遅延評価なのか遅延評価でないのか、微妙な感じです。preの計算までは即時で、その後は遅延されています。まるごと遅延したい場合はIxのDeferというメソッドが使えます。

// Deferで生成を遅延する
static IEnumerable<string> DropStartsSame2(params string[] args)
{
    return EnumerableEx.Defer(() =>
    {
        var pre = args.First()
            .Scan("", (x, y) => x + y)
            .TakeWhile(x => args.All(y => y.StartsWith(x)))
            .LastOrDefault() ?? "";
        return args.Select(x => x.Substring(pre.Length));
    });
}
 
// もしくはyield returnを使ってしまうという手も私はよく使っていました
static IEnumerable<string> DropStartsSame3(params string[] args)
{
    var pre = args.First()
        .Scan("", (x, y) => x + y)
        .TakeWhile(x => args.All(y => y.StartsWith(x)))
        .LastOrDefault() ?? "";
    var query = args.Select(x => x.Substring(pre.Length));
 
    foreach (var item in query) yield return item;
}
 
// 勿論、全部LINQで組んでしまってもOK
static IEnumerable<string> DropStartsSame4(params string[] args)
{
    return args.First()
        .Scan("", (x, y) => x + y)
        .TakeWhile(x => args.All(y => y.StartsWith(x)))
        .StartWith("") // linq.jsではInsert(0, [])でした
        .TakeLast(1) // linq.jsではTakeFromLastでした
        .SelectMany(x => args.Select(y => y.Substring(x.Length)));
}

私はIx以前はyield returnを結構よく使ってました。今は、Deferのほうが、例えば if(args == null) throw new ArgumentNullException(); とかがそのまま書けるのでDeferを選びたいかも。この辺の評価タイミングの話は前回、詳説Ix Share/Memoize/Publish編(もしくはyield returnの注意点)で書きました。

まとめ

というわけで、Scanの使い方でした。Scan可愛いよScan。ようするにAggregateの計算途中も列挙する版なわけなので、これ、標準クエリ演算子にも入って欲しかったなあ。結構使えるシーン多いです。

ああ、あとJavaScriptでもforなんて使いません(キリッ。linq.jsは真面目に普通に多機能なので遅い、じゃなくて、いや、それはまあ事実なんですが、便利には違いないです。他の普通のコレクションライブラリじゃ出来ないことも平然と出来ます。でもかわりに(ry

LINQ to Objects & Interactive Extensions & linq.js 全メソッド概説

@ITに以前書いたLINQの基礎知識の話が載りました -> LINQの仕組み&遅延評価の正しい基礎知識 - @IT。ああ、もっとしっかり書いていれば(図もへっぽこだし)、と思ったり思わなかったり。それでも校正していただいたのと、細部は修正してあるので、元のものよりも随分と読みやすいはずです。そういえばで1月頭の話なんですね、姉妹編としてRxの基礎知識もやるつもりだったのにまだやってないよ!

ところでそもそも基礎知識といったら標準クエリ演算子が何をできるかではないのでしょうか?知ってるようで知らない標準クエリ演算子。101 LINQ SamplesもあるしMSDNのリファレンスは十分に充実していますが、しかし意外と見逃しもあるかもしれません。また、Interactive Extensionsで何が拡張されているのかは知っていますか?ついでにJS実装のlinq.jsには何があるのか知っていますか?

そんなわけで、LINQ to Objects、Ix、linq.jsの全メソッドを一行解説したいと思います。

LINQ to Objects

いわゆる、標準クエリ演算子。.NET 3.5から使えます。.NET4.0からはZipメソッドが追加されました。なお、サンプルと実行例はlinq.js Referenceに「完全に」同じ挙動をするJS実装での例がありますので、そちらを参照にどうぞ。こういう場合はJS実装だと便利ですね。

Aggregate 汎用的な値算出
All 条件に全て一致するか
Any 条件に一つでも一致するか、引数なしの場合は空かどうか
AsEnumerable IEnumerable<T>へアップキャスト
Average 平均
Cast 値のダウンキャスト、主な用途はIEnumerableからIEnumerable<T>への変換
Concat 引数のシーケンスを後ろに連結
Contains 値が含まれているか、いわばAnyの簡易版
Count シーケンスの件数
DefaultIfEmpty シーケンスが空の場合、デフォルト値を返す(つまり長さ1)
Distinct 重複除去
ElementAt 指定インデックスの要素の取得
ElementAtOrDefault 指定インデックスの要素の取得、なければデフォルト値を返す
Empty 空シーケンスの生成
Except 差集合・差分だけ、集合なので重複は除去される
First 最初の値の取得、ない場合は例外が発生
FirstOrDefault 最初の値を取得、ない場合はデフォルト値を返す
GroupBy グループ化、ToLookupの遅延評価版(ただしストリーミングでの遅延評価ではない)
GroupJoin 右辺をグループにして結合、外部結合をしたい時にDefaultIfEmptyと合わせて使ったりもする
Intersect 積集合・共通の値だけ、集合なので重複は除去される
Join 内部結合
Last 最後の値を取得、ない場合は例外が発生
LastOrDefault 最後の値を取得、ない場合はデフォルト値を返す
LongCount シーケンスの件数、longなので長い日も安心
Max 最大値
Min 最小値
OfType 指定した型の値だけを返す、つまりWhereとisが組み合わさったようなもの
OrderBy 昇順に並び替え
OrderByDescending 降順に並び替え
Range 指定個数のintシーケンスの生成
Repeat 一つの値を繰り返すシーケンスの生成
Reverse 逆から列挙
Select 射影、関数の第二引数はインデックス
SelectMany シーケンスを一段階平らにする、モナドでいうbind
SequenceEqual 二つのシーケンスを値で比較
Single 唯一の値を取得、複数ある場合は例外が発生
SingleOrDefault 唯一の値を取得、複数ある場合はデフォルト値を返す
Skip 指定個数だけ飛ばす
SkipWhile 条件が正のあいだ飛ばす
Sum 合計
Take 指定個数列挙、シーケンスの個数より多く指定した場合はシーケンスの個数分だけ
TakeWhile 条件が正のあいだ列挙
ThenBy 同順の場合のソートキーの指定、昇順に並び替え
ThenByDescending 同順の場合のソートキーの指定、降順に並び替え
ToArray 配列に変換
ToDictionary 辞書に変換
ToList リストに変換
ToLookup 不変のマルチ辞書(一つのキーに複数の値を持つ)に変換
Union 和集合・両方の値全て、集合なので重複は除去される
Where フィルタ
Zip 二つのシーケンスの結合、長さが異なる場合短いほうに合わされる

暗記する必要はなくて、なんとなくこういうのがあってこんな名前だったかなー、とぐらいに覚えておけば、IntelliSenseにお任せできるので、それで十分です。

リスト処理という観点からみるとLINQはかなり充実しているわけですが、更に他の言語と比較した場合の特色は、やはりクエリ構文。SelectManyへの構文は多くの言語が備えていますが(モナドの驚異を参照のこと、LINQはLINM:言語統合モナドである、というお話)、SQLの構文をベースにしたJoin、GroupBy、OrderByへの専用記法は、意外と、というか普通に便利。

特にJoinはあってよかったな、と思います、インメモリで色々なところからデータ引っ張ってきて結合などすると特に。一つぐらいの結合なら別にメソッド構文でいいのですが、フツーのSQLと同じように大量のjoinを並べる場合に、クエリ構文じゃないとシンドい。インメモリからデータベースまで統一的な記法で扱える、ということの凄さを実感するところ。

といっても、普段はほとんどメソッド構文で書いてるんですけどねー。あくまで、込み入った状況になるときだけクエリ構文にしています。クエリ構文では表現できないものが結構多いわけで、わざわざ、これはクエリ構文だけで表現できるからクエリ構文にするかー、とか考えるのもカッタルイので。あと、単純にIntelliSenseでポコポコ打ってるほうが快適、というのもあります。

クエリ構文は、モナドへの記法というよりも、強力なリスト内包表記といった印象も、HaskellへのOrder By, Group Byのペーパー見て思ったりなんかしたりして。

Ix

Ix(Interactive Extensions)はReactive Extensionsで、現在は実験的なものとして提供されている、Enumerableの拡張メソッド群。NuGetのIx_Experimental-Mainで入れるのが使いやすい感じ。InfoQ: LINQ to Objectsのためのインタラクティブエクステンションに解説が少し出ていましたが、少し不足していたり、間違っていたり(DoWhileとTakeWhileは一見似ていますが、挙動は全然異なるし、Forは別に全く興味深くなくSelectManyと同じです)したので、こちらの方が正しいです(キリッ

Buffer 指定個数分に区切って配列で値を列挙
Case 引数のIDictionaryを元に列挙するシーケンスを決める、辞書に存在しない場合はEmpty
Catch 例外発生時に代わりに後続のシーケンスを返す
Concat 可変長引数を受け入れて連結する生成子、拡張メソッド版はシーケンスのシーケンスを平らにする
Create getEnumeratorを渡し任意のIEnumerableを生成する、といってもEnumerator.Createがないため、あまり意味がない
Defer シーケンスの生成をGetEumerator時まで遅延
Distinct 比較キーを受け入れるオーバーロード
DistinctUntilChanged 同じ値が続くものを除去
Do 副作用として各値にActionを適用し、値をそのまま列挙
DoWhile 一度列挙後に条件判定し、合致すれば再列挙
Expand 幅優先探索でシーケンスを再帰的に平らにする
Finally 列挙完了時に指定したActionを実行
For SelectManyと一緒なので存在意義はない(Rxと鏡にするためだけに存在)
ForEach foreach、関数の第二引数はインデックス
Generate forループを模した初期値、終了判定、増加関数、値成形関数を指定する生成子
Hide IEnumerable<T>に変換、具象型を隠す
If 条件が正なら指定したシーケンスを、負なら指定したシーケンス、もしくはEmptyで列挙する
IgnoreElements 後に続くメソッドに何の値も流さない
IsEmpty シーケンスが空か、!Any()と等しい
Max IComparer<T>を受け入れるオーバーロード
MaxBy 指定されたキーで比較し最大値だった値を返す
Memoize メモ化、複数回列挙する際にキャッシュされた値を返す
Min IComparer<T>を受け入れるオーバーロード
MinBy 指定されたキーで比較し最小値だった値を返す
OnErrorResumeNext 例外が発生してもしなくても後続のシーケンスを返す
Publish ShareとMemoizeが合わさったような何か
Repeat 無限リピート生成子、拡張メソッドのほうは列挙後に無限/指定回数最列挙
Retry 例外発生時に再度列挙する
Return 単一シーケンス生成子
Scan Aggregateの算出途中の値も列挙する版
SelectMany 引数を使わず別のシーケンスに差し替えるオーバーロード
Share 列挙子を共有
SkipLast 後ろからn個の値をスキップ
StartWith 先頭に値を連結
TakeLast 後ろからn個の値だけを列挙
Throw 例外が発生するシーケンス生成子
Using 列挙完了後にDisposeするためのシーケンス生成子
While 列挙前に条件判定し合致したら列挙し、終了後再度条件判定を繰り返す生成子

みんな実装したことあるForEachが載っているのが一番大きいのではないでしょうか。別に自分で実装するのは簡単ですが、公式に(といってもExperimental Releaseですが)あると、全然違いますから。なお、何故ForEachが標準クエリ演算子にないのか、というのは、“foreach” vs “ForEach” - Fabulous Adventures In Codingによれば副作用ダメ絶対とのことで。納得は……しない。

Ixに含まれるメソッドは標準クエリ演算子では「できない」もしくは「面倒くさい」。Ixを知ることは標準だけでは何ができないのかを知ること。何ができないのかを知っていれば、必要な局面でIxを使うなり自前実装するなりといった対応がすぐに取れます、無理に標準クエリ演算子をこねくり回すことなく。例えばBufferやExpandは非常に有益で、使いたいシチュエーションはいっぱいあるんですが、標準クエリ演算子ではできないことです。

While, DoWhileとTakeWhileの違いは条件判定する箇所。While,DoWhileは列挙完了前/後に判定し、判定がtrueならシーケンスを再び全て列挙する。TakeWhileは通る値で毎回判定する。

PublishとMemoizeの違いは難解です。Memoizeは直球そのままなメモ化なんですが、Publishが凄く説明しづらくて……。Enumerator取得まではShareと同じく列挙子の状態は共有されてるんですが、取得後はMemoizeのようにキャッシュした値を返すので値の順番は保証される、といった感じです。うまく説明できません。

存在意義が微妙なものも、それなりにありますね。例えばIfとCaseとForなどは、正直、使うことはないでしょう。Usingも、これを使うなら別メソッドに分けて、普通にusing + yield returnで書いてしまうほうが良いと私は考えています。

Ixを加えると、ほとんど全てをLINQで表現出来るようになりますが、やりすぎて解読困難に陥ったりしがちなのには少し注意を。複雑になるようならベタベタ書かずに、一定の塊にしたものを別メソッドに分ければいいし、分けた先では、メソッドを組み合わせるよりも、yield returnで書いたほうが素直に表現出来るかもしれません。

適切なバランス感覚を持って、よきLINQ生活を!

linq.js

LINQ to ObjectsのJavaScript実装であるlinq.jsにも、標準クエリ演算子の他に(作者の私の趣味で)大量のメソッドが仕込んであるので、せっかくなのでそれの解説も。標準クエリ演算子にあるものは省きます(挙動は同一なので)。また、C#でIEqualityComparer<T>を受け取るオーバーロードは、全てキーセレクター関数のオーバーロードに置き換えられています。

一行サンプルと実行はlinq.js Referenceのほうをどうぞ。

Alternate 値の間にセパレーターを織り込む、HaskellのIntersperseと同じ
BufferWithCount IxのBufferと同じ、次のアップデートでBufferに改称予定
CascadeBreadthFirst 幅優先探索でシーケンスを再帰的に平らにする、IxのExpandと同じ
CascadeDepthFirst 深さ優先探索でシーケンスを再帰的に平らにする
Catch IxのCatchと同じ
Choice 引数の配列、もしくは可変長引数をランダムに無限に列挙する生成子
Cycle 引数の配列、もしくは可変長引数を無限に繰り返す生成子
Do IxのDoと同じ
Finally IxのFinallyと同じ
Flatten ネストされた配列を平らにする
Force シーケンスを列挙する
ForEach IxのForEachと同じ
From 配列やDOMなど長さを持つオブジェクトをEnumerableに変換、linq.jsの要の生成子
Generate ファクトリ関数を毎回実行して値を作る無限シーケンス生成子、IxのGenerateとは違う(IxのGenerateはUnfoldで代用可)
IndexOf 指定した値を含む最初のインデックス値を返す
Insert 指定したインデックスの箇所に値を挿入、Insert(0, value)とすればIxのStartWithと同じ
LastIndexOf 指定した値を含む最後のインデックス値を返す
Let 自分自身を引数に渡し、一時変数を使わず自分自身に変化を加えられる
Matches 正規表現のマッチ結果をシーケンスとして列挙する生成子
MaxBy IxのMaxByと同じ
MemoizeAll IxのMemoizeと同じ、次のアップデートでMemoizeに改称予定
MinBy IxのMinByと同じ
Pairwise 隣り合う要素とのペアを列挙
PartitionBy キーで指定した同じ値が続いているものをグループ化する
RangeDown 指定個数のマイナス方向数値シーケンス生成子
RangeTo 指定した値まで(プラス方向、マイナス方向)の数値シーケンス生成子
RepeatWithFinalize 単一要素の無限リピート、列挙完了時にその要素を受け取る指定した関数を実行
Return IxのReturnと同じ
Scan IxのScanと同じ
Share IxのShareと同じ
Shuffle シーケンスをランダム順に列挙する
TakeExceptLast IxのSkipLastと同じ
TakeFromLast IxのTakeLastと同じ
ToInfinity 無限大までの数値シーケンス生成子
ToJSON シーケンスをJSON文字列に変換(組み込みのJSON関数のあるブラウザかjson2.jsの読み込みが必要)
ToNegativeInfinity マイナス無限大までの数値シーケンス生成子
ToObject JSのオブジェクトに変換
ToString 文字列として値を連結
Trace console.logで値をモニタ
Unfold Aggregateの逆、関数を連続適用する無限シーケンス生成子
Write document.writelnで値を出力
WriteLine document.writeln + <br />で値を出力
TojQuery シーケンスをjQueryオブジェクトに変換
toEnumerable jQueryの選択している複数の要素を単一要素のjQueryオブジェクトにしてEnumerableへ変換
ToObservable 引数のSchduler上で(デフォルトはCurrentThread)Observableへ変換
ToEnumerable Cold ObservableのみEnumerableへ変換

Ixと被るものもあれば、そうでもないものも。ToStringなどは分かりやすく便利でよく使うのではかと。ToJSONもいいですね。Fromは拡張メソッドのない/prototype汚染をしないための、JavaScriptだけのためのメソッド。Matchesは地味に便利です、JSの正規表現は使いやすいようでいて、マッチの列挙はかなり面倒くさいので、そこを解消してくれます。linq.jsは移植しただけ、ではあるんですが、同時に移植しただけではなくて、JavaScriptでLINQはどうあるべきか、どうあると便利なのか、という考えに基づいて調整されています。

JavaScriptにはyield returnがないので(Firefoxにはyieldありますが)、シーケンスは全て演算子の組み合わせだけで表現できなければならない。というのが、手厚くメソッドを用意している理由でもあります。これだけあれば何だって作れるでしょう、きっと多分恐らく。

まとめ

これで今日からLINQ to Objectsマスター。Rx版もそのうち書きます(以前にReactive Extensions入門 + メソッド早見解説表を書きましたが、今は結構変わってしまいましたからね)。

linq.js入門記事を書きました

お話をいただき、@ITの.NET TIPSにlinq.jsの入門記事を、二週に渡り書きました。

このサイトでやると、アレもコレもとダラダラと書いてしまって分かりづらくなっていたのですが、記事では文字数制限などのお陰で構成がすっきり、校正してもらったお陰で文章の揺れもなく。つまるところ、ほとんど編集で助けてもらったというだけで、本当にありがとうございました。サンプルコードは、コードを見ただけで伝わるよう単純に、でもlinq.jsの威力を伝えなければならないので多少の複雑さは持たなければならない。などと思い結構悩んで作りました。

お陰様で反響も結構良かったみたいでなによりです。実績も良く分からない外部ライブラリは導入できない…… という方も、@ITに載ってるから大丈夫だよ!を説得材料(?)にできるのではないでしょうか。これを機に、是非試してみてください。

ところで幾つかの話。

メソッド名大文字

失敗した。かな……。特にTojQuery()でjQueryと見かけ上(あくまで見かけだけなんですが)シームレスに繋がっていると違和感が結構あります。以前に、jQueryとLINQの世界が視覚上切れて見えるから、むしろイイぐらいなんです、とか言ってましたが勿論ただの強がりです。この辺は今更変えにくいところで、どうしたものかな、と悩んでいるところです。

パフォーマンスについて

気になりますよね?ベンチマーク的に言えば、遅い。遅延評価の実現や豊富な機能は、速度を相当犠牲にしています。では、その遅さが許容できるほどか無視できないか。これは、卑怯な逃げ口になってしまいますが、状況次第。少なくとも普通のサイトでは問題ないレベルだと思いますし、また、ブラウザのJSはどんどん速くなっていってます。Chrome/Fx4/IE9の速さ!「許容できる」の範囲はどんどん広がっていってるのでは、と。

こういった話はC#でもそうです。LINQはベタforループより確実に遅い、が、じゃあベタforループで書くかといったら、よほどエクストリームに速度を求める場合以外は書きはしません。

Pentium3でWindows XPでIE6な奴らにも配慮する!というのも確かに美学なのですが、そうではない方向も見ないと、素敵な未来はやってこないのではないかな、って。21世紀にもなるのにループアンローリングで高速化!とかいう記事ばかりが踊る世界なんて悲しいじゃないですか。

モバイル機器のことも考えなければならないし、HTML5も控え、JavaScriptで高負荷な処理をすることも少なくないので、まだまだ時代は追いついていないけれど。それでも、私はもう少し先の未来を見ていたい……。JavaScriptがこれから先、本当に一級の言語となっていくのなら尚の事です。

NuGetパッケージの作り方、或いはXmlエディタとしてのVisual Studio

linq.js 2.2.0.2をリリースし、今回からNuGetでも配信されるようになりました!linq.js、もしくはlinq.js-jQuery、linq.js-Bindingsで入りますので、是非お試しを。ちなみに更新事項はちょっとBugFixとOrderByの微高速化だけです(本格的な変更は次回リリースで)。

さて、そんなわけでNuGetに対応したので、今回はNuGetのパッケージの作り方、公開のしかたについて解説します。やってみると意外と簡単で、かつNuGetいいよNuGet、と実感出来るので、特に公開するようなライブラリなんてないぜ!という場合でも試してみるのがお薦め(参照先としてローカルフォルダも加えられる)。普通に小さなことでも使いたくなります。そういえばでどうでもいいんですが、私は「ぬげっと」と呼んでます。GeForceを「げふぉーす」と呼ぶようなノリで。ぬげっと!

NuGetを使う

NuGetとは何ぞやか、大体のとこで言うと、オンラインからDLLとかライブラリをサクッと検索出来て、依存関係(これのインストールにはアレとソレが必要、など)を解決してくれた上で参照に加えてくれて、ついでにアップデートまで管理してくれるものです。Visual Studioの拡張として提供されているので、インストールはCodePlexからでもいいですが、VSの拡張機能マネージャからNuGetで検索しても出てきます。

インストールすると参照設定の右クリックに「Add Library Package Reference」というのが追加されていて、これを選択すると、トップの画像のようなNuGetの参照ダイアログが出てきます。最初NuGetが喧伝されていたときはPowerShellでのConsoleでしたが、ご覧のようにGUIダイアログもあるので安心。Consoleのほうが柔軟でパワフルな操作が可能なのですが(PowerShellを活かしたパイプやフィルタで一括ダウンロードとか)、普通に参照してーアップデートしてー、程度ならば別にGUIでも全然構いませんし。

.nupkg

NuGetを通してインストール/参照を行うと、プロジェクトのフォルダにpackages.configが生成されています。しかしこれはどうでもいいのでスルー。.slnのあるディレクトリにpackagesというフォルダも生成されていて、実体はこちらにあります。そこにはパッケージ名のフォルダが並んでいて、中には.nupkgという見慣れないものと、libもしくはContentというフォルダがあるのではないでしょうか……?

nupkgが最終的に作らなければならないもので、実態はただのzip。nupkgと同フォルダにあるlib/Contentはnupkgが展開された結果というだけです。というわけで、適当なパッケージをダウンロードして(linq.jsとかどうでしょう!)zipにリネームして解凍するとそこには……!

_relsとか[Content_Types].xmlとか、わけわからないものが転がってます。これらはノイズです。ようするに、System.IO.ZipPackageを使って圧縮してるというだけの話ですねー、恐らくこれらがある必然性はないです。ただたんに、.NET Framework標準ライブラリだけでZipの圧縮展開をしようとすると、こうしかやりようがなかったという、ただそれだけです。だから早くZipライブラリ入れてください(次辺りに入るらしい)。

大事なのは、.nuspecです。

.nuspec

.nuspec(中身はXml)に、バージョン情報やID、依存関係などが記載されています。といったわけで、自分で作らなければならないのは.nuspecです。これにパッケージしたいファイルや配置場所などの定義を記述し、それをNuGet.exeというものに通すと.nupkgが出来上がる、といった流れになっています。

nuspecの記述には、既存のnuspecを見るのが参考になるかもでしょう。但し、既存のnupkgを落として展開した結果のnuspecはNuGet.exeを通された時点で再加工されているものなので(パッケージ用のファイルの場所などの情報は消滅してる←まあ、絶対パスで記述可能だったりするので消滅してないと逆に困るわけですが)、100%そのまんま使える、というわけではないことには少し注意。

XmlエディタとしてのVisual Studio

では、nuspecを書いていく、つまりXmlを書いていくわけですがエディタ何使います?勿論Visual Studioですよね!Visual Studioは最強のXmlエディタ。異論はない。えー、マジXmlを補完無しで書くなんてシンジラレナーイ!小学生までだよねキャハハ。というわけで、補完全開で書きます。補完さえあればリファレンスなくても書けるし!IntelliSense最強説。

そのためにはスキーマが必要なわけですが、ちゃんと用意されています。NuGet Documentationの下の方のReferenceの.nuspec File Schemaにスキーマがリンクされています。CodePlexのソースリポジトリに直リンクというのが色々潔いですな。

さて、適当に新規項目でXmlを作ったら、メニューのXML->スキーマのダイアログを開き、nuspec.xsdを追加してやりましょう。

そして、とりあえずは<とでも打ってやると補完に!–とか!DOCTYPEなどなどに並んでpackageというものが。これを選択すると、一気にxmlns=”http…”と名前空間まで補完してくれて!更に更に書き進めれば……。入力補完は効くし、必須要素が足りなければ警告出してくれるしでリファレンスとか何も見なくても書ける。

これなら打ち間違えでエラーなども出ないし、完璧。Xmlなんて普通のテキストエディタで気合で書く、とか思っていた時もありました。もうそんなの無理げ。VSバンザイ。なお、FirefoxのアドオンのGUI定義などに使うXULのSchemaなども当然適用出来る - XUL Schema ので、まあ、補完のないテキストエディタなんて使ってたら死んでしまうです。

なお、毎回毎回、スキーマの追加参照するのは面倒くさいという場合は、VSの標準スキーマ参照ディレクトリにxsdを直に突っ込んでおくと、楽になれます。オプション->テキストエディター->XMLでスキーマ、で場所が設定出来ます(デフォルトは %VsInstallDir%\xml\Schemas のようで)

パッケージング

nuspecのリファレンスは.nuspec File Formatに。IDとかVersionとかしか書かないし、項目も少ないしネストもないので書き方というほど書き方はないです。参考までにlinq.js-jqueryのnuspecは

<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <metadata>
        <id>linq.js-jQuery</id>
        <version>2.2.0.2</version>
        <title>linq.js - jQuery Plugin Version</title>
        <authors>neuecc</authors>
        <owners>neuecc</owners>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <description>Linq to Objects for JavaScript. This version is plugin integrated with jQuery.</description>
        <language>en-US</language>
        <licenseUrl>http://linqjs.codeplex.com/license</licenseUrl>
        <projectUrl>http://linqjs.codeplex.com/</projectUrl>
        <tags>linq javascript jquery</tags>
        <dependencies>
            <dependency id="jQuery" version="[1.3.1,]"></dependency>
        </dependencies>
    </metadata>
    <files>
        <file src="../../jquery.*" target="Content\Scripts" />
    </files>
</package>

といった感じ。tagsはスペース区切りで入れておくと検索の時にそのワードで引っかかる。dependenciesは依存関係がある場合に記載。対象バージョンの書き方に関してはSpecifying Version Ranges in .nuspec Filesを見て書くべし。

filesは後述するNuGet.exe(コマンドラインツール)でのパッケージング時に参照するファイルを設定。何も記載しない場合はNuGet.exeの実行時引数で解決されるので、どちらでもお好みで、という感じですが、普通はこちらに書いておいたほうが楽な気はします。

ファイル指定のsrcではワイルドカードとして*が使えます。targetのほうは、nupkgにパッケージングされた時の階層の指定。この階層の指定は非常に重要です。「Content」の下に記載すると、プロジェクト直下に対象を配置します。この例では Scripts 下に「jquery.linq.js, jquery.linq.min.js, jquery.linq-vsdoc.js」が展開されることになっています。Scriptsというフォルダ名はjQueryに合わせてあります。勿論、対象は.csでも.txtでも何でも可。

では、普通のC#でのdllのように直下には.dllとか置いて欲しくないし参照設定にも加えて欲しい、という場合はどうするかというとtargetを「lib」にします。すると自動で参照設定に加えてくれます。この「Content」とか「lib」とかってのは名前で決め打ちされてますので、そーいうものだと思うことにしませう。

残るはパッケージ化。まずNuGetのトップからDownloadsタブ(Downloadボタンじゃなく)を選び、NuGet Command Line Toolをダウンロード。このNuGet.exeに対して引数「p ファイル名」でnuspecを指定してやればnupkgが出来上がります。私はnuspecと同じ階層にexeを置いて、ついでにbatファイルに

nuget p linq.js.nuspec
nuget p linq.js-jquery.nuspec
nuget p linq.js-bindings.nuspec

とか書いたのを置いて3個のパッケージを作ってます。この辺は好き好きで。

以上が基本的な感じです。ただたんに参照設定に加える、ファイルを配置する、以上のことをやりたい場合はインストール時にPowerShellスクリプトを実行、なども出来るので色々柔軟に手を加えられそうです。また、.NET Frameworkのバージョンによって参照させるファイルを変える、といったことはフォルダの構成を変えるだけで対応で可能です。例えば.Net4の場合は lib/Net4 に、Silverlightへは lib/SL4 に、といったような感じ。

といったルールなどはNuGet Creating a Packageを見るといいでしょう。また、バージョンのフォルダ分けがワケワカランという場合は既存のnupkgを展開してフォルダ構成を見てしまうのが手っ取り早いかも。Rx-AllやNewtonSoft.Jsonなどなど。

ローカル参照としてのNuGet

nupkgは別にオフィシャルのサーバーだけではなく、個人で立てたサーバーも参照出来ます。また、それだけでなく、ただたんにフォルダを指定するだけでもOKです。

作ったnupkgはこれでテスト可能です。また、頻繁に参照に加えるものはわざわざOnlineに繋げて取ってくるの重い!という場合では一度落としたnupkgをローカルに配置してしまうのも悪くないかもです。テストというだけじゃなく、これは普通に使えますね?今まで参照設定の共通化というとテンプレート作って、程度しかありませんでしたが、これならばいい具合に自由度の効いたものが出来そうです。社内/俺々フレームワーク置き場として活用できそう。

なお、現在は、ローカル参照のパッケージは、GUIのパッケージマネージャだとバージョンが上がってもUpdatesに現れなくてアップデート出来ません。Consoleならば現れるので、ふつーにバグのよう。で、報告されていましたし修正もされていた(今リリースされているのには反映されてないもよう)ので、次のリリースでは直ってるんじゃないかと思われます。

NuGet gallery

せっかく作ったパッケージはOnlineに乗せたいよね!NuGet galleryでパッケージの閲覧・登録・管理が出来ます。よーし、じゃあパパSign Inしちゃうぞー、Registerして、と。やってもいつまでたってもInvalid Passwordと言われてしまいます。あれれ……。

現在は管理者の承認が必要なようで David Ebbo: Introducing the NuGet gallery Registerしたら、Twitterの@davidebbo宛てにapproveして!と言わないとダメぽ。私は「Hi. I registered nuget.org, id is “neuecc” . plaease approve my account.」と、スペルミスしてる適当不躾な@を飛ばしたところ数時間後にSign In出来るようになりました。いつまで認証制なのかは不明ですが、いまんところそんな感じなようです。

まとめ

オンラインで簡単にDLLをインストール出来て便利!というのは勿論ありますが、ローカルで使ってみても存外便利なものです。ぬげっといいよぬげっと。NuPackからNuGetに名前が変わったときは、事情は分かる(NuPackは名前が被ってたらしい)けど、NuGetはないだろ、いくらなんでも。と、思ってたんですが、今は何かもうすっかり馴染んだ気がします。ぬげっと。ぬぱっけーじ。ぬすぺっく。

とりあえず私は今後作るのは勿論、今まで出してきたものも、順次対応させてNuGet galleryに登録していくのでよろしくお願いしま。勿論linq.jsもよろしくお願いしま。今回の2.2.0.1は表には何も更新されてない感じですが、裏側の体制を整えてました。

F#スクリプト(fsx)により、linq.jsからAjaxMinのdllを通し圧縮化と、ついでにjQueryプラグインを生成したり、これまたF#スクリプトでリリース用のZip圧縮をワンクリックで一発で出来るようにしたり。今まで手動でやっていた(そしてミスしまくってた!リリースから10分で撤回して上げなおしとか今まで何度やってきたことか)部分を完全自動化したので、もうミスはありません。そして、自動化されたことによりリリースはミス出すし面倒なので、もう少し色々やってからにするかー、とズルズル後回しにする心理がなくなりました。多分。きっと。NuGet対応したことだしで、当分はアクティブにアップデートしていきます!

そんなこんなでF#スクリプトはぢめました。素晴らしすぎる。あとVS2010とシームレスに完全統合されたF# Interactiveがヤバい。超凄い。こんなイイものがあったなんて……。というわけでF#書きたい欲とF#について色々書きたい欲が、ので次回は実践F#書評です、多分。いや、次々回かも。とりあえず近日中には。とにかくF#は絶対触るべきですね!

おまけ

と、いうわけで、生成を自動化します。F#スクリプトでdllのアセンブリ情報を読み込んでnuspecとnupkgを生成するものを書きました。

#r "System.Xml.Linq"
 
open System
open System.IO
open System.Diagnostics
open System.Reflection
open System.Xml.Linq
 
// 同ディレクトリにNuGet.exeを置いておくこと
// mainにはnuspecへの情報登録に利用するdllを、othersにはその他のものを;区切りで
// パスはこのスクリプトからの相対パス
let main = "bin/Release/ClassLibrary4.dll"
let others = ["bin/Release/System.CoreEx.dll"; "bin/Release/System.Interactive.dll"]
 
let pass p = Path.Combine(__SOURCE_DIRECTORY__, p)
let xn s = XName.Get(s)
 
// Load Assembly
type AssemblyInfo =
    { Id:string; Version:string; Description:string; Company:string }
 
let getAttr<'a> (asm:Assembly) = 
    asm.GetCustomAttributes(typeof<'a>, true) |> Seq.head :?> 'a
 
let info =
    let asm = Assembly.LoadFrom(pass main)
    let name = asm.GetName()
    { Id = name.Name;
      Version = name.Version.ToString();
      Description = (getAttr<AssemblyDescriptionAttribute> asm).Description;
      Company = (getAttr<AssemblyCompanyAttribute> asm).Company }
 
let filename = info.Id + "." + info.Version + ".nuspec"
 
// Build .nuspec
let nuspec =
    let file src = XElement(xn "file", XAttribute(xn "src", src), XAttribute(xn "target", "lib"))
    let delBlank = function "" -> "_" | x -> x
    XElement(xn "package",
        XElement(xn "metadata",
            XElement(xn "id", info.Id),
            XElement(xn "version", info.Version),
            XElement(xn "authors", delBlank info.Company),
            XElement(xn "description", delBlank info.Description)),
        XElement(xn "files",
            file main,
            others |> Seq.map file))
 
nuspec.Save(pass filename)
 
// output .nupkg
new ProcessStartInfo(
    FileName = pass "NuGet.exe",
    Arguments = "p " + filename,
    RedirectStandardOutput = true,
    UseShellExecute = false,
    WorkingDirectory = __SOURCE_DIRECTORY__)
|> Process.Start
|> fun p -> Console.WriteLine(p.StandardOutput.ReadToEnd())

DLLからVersionとかDescriptionとか取れてしまう、.NETのアセンブリがサクッと読み込めるF#いいわー。これだけだと情報は最低限なので、tagとかも入れたければ下の方のXElementを生成している部分に直書きで挟んでやればヨシ。スクリプトの軽快さは良いですね。なので設定というか読み込むファイルも先頭のほうで普通に直書きで指定しちゃっております。

そのまま使ってもいいんですが、ビルド後に実行するコマンドラインに指定してやれば一切の手間暇なく常にフレッシュ。おお、素敵。

Linqと総当り

各所でAdvent Calendarも終了し皆様お疲れさまでした。自分のよく知っている言語はもちろんですが、他の言語を見ていても非常に楽しめて良いですね、特に普段自分の書かない言語で、入門からちょっと突っ込んだものまでまとめて見れるというのは非常に良い機会でした。

そんな中で見かけたScalaによる リストモナドを使ってみる - みずぴー日記 という記事。おお、スッキリかっこよく求まりますね、Scalaカコイイ。さて、総当り。非決定性計算。リストモナド。といったら、Linq、クエリ式。そんな風に反射的に連想が成りたつならもう立派なLinq使いですね!いやまあ、クエリ式で総当たり - NyaRuRuの日記で読んだことがあるから、というだけの話なのですがー。ほとんど直訳でいけるとあるように、Scalaで書かれたsend + more = moneyもまた、直訳で書けました。

var digits = Enumerable.Range(0, 10);
 
var solve = from s in digits
            from e in digits.Except(new[] { s })
            from n in digits.Except(new[] { s, e })
            from d in digits.Except(new[] { s, e, n })
            from m in digits.Except(new[] { s, e, n, d })
            from o in digits.Except(new[] { s, e, n, d, m })
            from r in digits.Except(new[] { s, e, n, d, m, o })
            from y in digits.Except(new[] { s, e, n, d, m, o, r })
            where s != 0
            where m != 0
            let send = int.Parse("" + s + e + n + d)
            let more = int.Parse("" + m + o + r + e)
            let money = int.Parse("" + m + o + n + e + y)
            where send + more == money
            select new { send, more, money };
 
foreach (var item in solve) Console.WriteLine(item);

ちゃんちゃん。こういうの見ると、各言語は同じとこに向かってる感がありますね。微妙に表現は違いますが、同じ発想が通じるし同じように書ける。その点はC#もScalaもF#も同じパラダイム、同じ未来を目指して向かってるのではないかと思います。Javaは微妙に落伍している気がしますが、もう少し何とかなって欲しいものです。

と、いうだけなのもアレなので、 int.Parse(”" + s + e + n + d) の部分について。数字を文字列的な並びとして解釈したい、というわけですが、手段をまとめるとこんな感じになるかしらん。

// という数字があった時に123にしたい
var x = 1;
var y = 2;
var z = 3;
 
// ダルい
var xyz1 = int.Parse(x.ToString() + y.ToString() + z.ToString());
// 複数ある時はこちらで
var xyz2 = int.Parse(string.Concat(x, y, z));
// コンパイル後の結果はxyz2と一緒
var xyz3 = int.Parse("" + x + y + z);
// 文字列変換しない
var xyz4 = x * 100 + y * 10 + z;
// ↑のは桁数が多い時に泣くのでこうしてやる
var xyz5 = new[] { x, y, z }.Aggregate((a, b) => a * 10 + b);

文字列にして並べてintに変換するのがお手軽なわけですが、.ToString()を並べていくのはダルいわけですよね!というわけで、そんな時はstring.Concatを使うと全部まとめて結合出来ます。しかし int.Parse(string.Concat と並ぶのすらダルい、とかいう不届き者は先頭に”"と足し合わせることでstring.Concatで結合したのと同じ結果を得られます。これは、演算子は左から適用されていきますが、文字列と数値を+で足すと文字列として足される、以下繰り返し。の結果。キモチワルイといえばキモチワルイので、積極的に使うかというと悩ましいところですが……。

そもそも数字を扱うのに文字列に変えてー、とかが邪道だという話も割とある。効率的な意味でも。なので、そういうときは2の桁は10倍、3の桁は100倍……。とかやっていると桁数が多いときはどうするのどうもしないの?という話なので、Aggregateを使うという手もあります。Aggregateは一般的な関数型言語でいうfoldlに相当。左からの畳み込み演算。ところで、では右からの畳み込みはどうすればいいの?つまりはfoldrはどうなのか、というと、これは.Reverse().Aggregate() のようで。右からなら逆にすればいいぢゃない。

ところで、C#のLinqで出来ることはJavaScriptのlinq.js - LINQ for JavaScriptでも出来ますよ?やってみます?

var digits = Enumerable.Range(0, 10);
 
var solve = digits
    .SelectMany(function(s){ return digits.Except([s])
    .SelectMany(function(e){ return digits.Except([s, e])
    .SelectMany(function(n){ return digits.Except([s, e, n])
    .SelectMany(function(d){ return digits.Except([s, e, n, d])
    .SelectMany(function(m){ return digits.Except([s, e, n, d, m])
    .SelectMany(function(o){ return digits.Except([s, e, n, d, m, o])
    .SelectMany(function(r){ return digits.Except([s, e, n, d, m, o, r])
    .Select(function (y) { return { s: s, e: e, n: n, d: d, m: m, o: o, r: r, y: y} })})})})})})})})
    .Where("$.s != 0")
    .Where("$.m != 0")
    .Select(function(x){ return { 
        send: parseInt("" + x.s + x.e + x.n + x.d),
        more: parseInt("" + x.m + x.o + x.r + x.e),
        money: parseInt("" + x.m + x.o + x.n + x.e + x.y)}})
    .Where(function (x) { return x.send + x.more === x.money });
 
solve.ForEach(function (x) { document.writeln(x.send + ":" + x.more + ":" + x.money) });

C#クエリ式からの変換のポイントは、from連鎖をSelectManyの連鎖で、但しカッコは閉じず変数のキャプチャを内包したままで最後にSelectで一旦整形してやるというところです。正確なクエリ式の再現とはなりませんが、この程度ならば、まあ何とか書けなくもないレベルとなります(正確なクエリ式の変形結果の再現をやると手では到底書けないものになる)。

ちなみに総当りなので結構時間がかかってIEだと泣きます。Chromeなら、まあそれなりな速度で求まるかなー。

linq.js & Reactive Extensions for JavaScript(RxJS)入門

このエントリはJavaScript Advent Calendar 2010 : ATNDの20日目として書きます。一つ前はsecondlifeさんのコマンドラインから JavaScript のシンタックスチェックを行う方法 - って、なんでですか〜 - subtechでした。

普段はC#でもしゃもしゃしている、@neuecc(twitter)といいます。そんなわけで今回はC#畑からのJavaScriptライブラリを二つほど紹介します。

ここではC#の中でも、LINQ: .NET 統合言語クエリという機能から来ているlinq.jsとRxJSを紹介します。linq.jsはコレクション操作を、RxJSはイベント操作と非同期操作に関するライブラリとなっています。どちらもwindowオブジェクトには一切触らない、DOMやXmlHttpRequestとは全く無関係の中立で地味な、phpspotで紹介されそうもない、それだけで何が出来るかというと別に何もできないけれど、あると便利。また、Enumerable(linq.js)とObservable(RxJS)という枠組みが双対であるという、面白い影響の与え方をしているため、セットで考えるとより深イイ話になるから、ちょっと長くなりますが二つ一緒に紹介します。

linq.js

linq.jsはLINQのうち、Linq to ObjectsというものをJavaScriptに移植したものとなります。ダウンロードは下記URLから。ライセンスはMs-PL、というと聞きなれないかもしれませんが、MITライセンスに近い、かなり緩めのものとなっています。

linq.js - LINQ for JavaScript

公式で移植されたものではなく、野良で勝手にやっているだけの話であり、作者は、私です。ん……。あ……。……。さて、LINQとは何ぞや、統合言語クエリがうんちゃらかんちゃら、SQLがどうのこうの、というのはJS的には全く関係ないのでスルーで!Linq to Objectsに絞って何ぞやというと、「関数型っぽいコレクション処理ライブラリ」です。ひとまず簡単な例から。

// こんなテキトーな配列があったとして
var array = [101, 20, 2, 42, 33, 47, 52];
 
// 偶数だけ二倍して出力 : 40, 4, 84, 104
Enumerable.From(array)
    .Where(function(x){ return x % 2 == 0 })
    .Select(function(x){ return x * 2 })
    .ForEach(function(x){ document.writeln(x) });

よくある関数を渡してコレクション処理するスタイルです。グローバルに配置される名前空間はEnumerable。そこから、既存の配列に対して適用する場合はFromで包んで(jQueryの$のようなものと考えればOK、数字列の場合はRangeなどもあります)、以降はLinqの専用メソッドをチェーンで繋いでいく、というスタイルを取ります。ちなみにEnumerableはイニュミラボーと読むようです。最後のボーは、私は日本人なのでブルって言いますが。イニュミラブル。

ところでfilterしてmapしてforeach。そうですFirefoxにあるArrayへのmap/filterです。Whereがfilter、Selectがmap。名前はSQL風味ですが、つまるところ単純な話そういうことです。フィルタぐらいならjQueryにもmap/grep/eachがあるね。他に目を向ければ、コレクション系専用ライブラリではUnderscore.jsが有名のようです。

それらとの違いですが、一つはfunction(){}という、冗長にすぎる関数記述を省く文字列による簡易記法の搭載。もう一つは完全な遅延評価の実現。そのため、適用する関数を自由に追加していくことができます。簡単な例をもう少し。

var array = [101, 20, 2, 42, 33, 47, 52];
 
// この時点ではまだ列挙されていない。
// $は引数を示すプレースホルダーで、この場合 function(x){ return x%2==0 } と同じ
var query = Enumerable.From(array).Where("$%2==0");
 
// 更に二乗したうえで配列へ変換するならToArray
var array2 = query.Select("$*$").ToArray(); // [400, 4, 1764, 2704]
// 昇順に並び替えた上でセパレータをつけて文字列化
var orderedStr = query.OrderBy().ToString(":"); // "2:20:42:52"
// 先頭二つのみを列挙
query.Take(2).ForEach("alert($)"); // 20, 2

と、いったように様々なメソッドを繋げてコレクションを変形し、最後に任意の形に変換するというのが基本的な利用法になるわけです。わからん。というわけで図にするとこんな感じです。

構図的にはjQueryと同じで、Enumerable.From/Range/etc.. によってEnumerableオブジェクトが生成され、その中でのメソッド(Select/Where/etc..)はEnumerableオブジェクトを返す。そのため幾らでもメソッドチェーンを繋げられる。jQueryと違うのは、繋げた関数が実行されるのは、戻り値がEnumerable以外になるときまで遅延される。Enumerable以外を返すメソッドとは、ToArray(配列に変換)、ToString(文字列に変換)、ToObject(オブジェクトに変換)、Contains(値が含まれていればtrue、含まれていなければfalse)、Max(最大値を返す)、ForEach(列挙する)、etc…(まだまだ大量にあります)。

遅延評価であることによるメリットとして、無限リストを扱えることが挙げられます。というわけで、無限リストを活用する例を一つ。「nを1から初めてその2乗を足していき、和が2000を初めて超えたとき和はいくつになるかという問題」をScalaで解いてみたから、nを1から(以下略)をlinq.jsで解いてみます。

var result = Enumerable.ToInfinity(1) // 1から無限大まで数値をジェネレート [1, 2, 3, 4,...]
    .Select("$*$") // 二乗 [1, 4, 9, 16,...]
    .Scan("$+$$") // 和 [1,5,14,30,...]
    .First("$>2000"); // 2000を超えた最初の要素

元がC#/Linq版のコードであるから当然ではありますが、リンク先のScala版のコードと完全に一致、ですね。JavaScriptは関数型言語。無限リストの生成には、今回はToInifinityを使いましたが、関数型言語に馴染みのある人ならばUnfoldなどもありますので、望む物が生成出来るはずです。

“$+$$”でサクッと和を出せるのは中々強力で便利。やりすぎるとイミフになるので、こういうササッとした部分にだけ限定すれば。任意の識別子を使いたい場合は”sum, x => sum + x”というように=>の左側は引数、右側は式、という形でも書けます。なお、実装は new Function(”$,$$,$$$,$$$$”, “return ” + expression) というだけです。渡す文字列が式でないとダメな非常に単純な理由。

その他、メソッド類の一覧と、その場で実行/確認可能なLINQ Padはlinq.js Referenceで。

といったように、リアルタイムに実行結果を確認しながら試せます。無限リストを停止条件つけないで書いてしまっても、列挙は1000件までにリミッターかかるので一安心。

メソッド群ですが、LINQにある標準クエリ演算子(と、言うと大仰ですが、ようするにただのメソッドです)を全て実装、その上でHaskellやRubyなどのコレクション用メソッドを眺めて、便利そうなら移植してあります。そのため、コレクションライブラリとしてはとしてこのメソッドが不足してる!と不満を感じることはないはず。また、遅延評価を活かしてメソッドを組み合わせることにより、大抵の操作が可能になっています。

とはいえ、そのせいで肥大しすぎな感がなきにしもあらず。とりあえず、フィルタ(Where)とマップ(Select)、非破壊的で複数キー連結が可能なソート(OrderBy/ThenBy)、重複した値を取り除く(Distinct)といった辺りだけ押さえておけば良いかなー、と。

そういえばでドットが前置なのは何で?というと、その方が入力補完に便利だからです。

この辺の話はJavaScriptエディタとしてのVisual Studioの使い方入門で。IDEを使ってJSを書くなら、ドットは前置一択になります。補完のない普通のエディタで書くのならスタイルはお好みで。私としては、入力補完・コードフォーマッタ・デバッガとのシームレスな融合などなどから、JavaScriptであってもIDEを使うのが良いと思ってます。

Reactive Extensions for JavaScript

続けてReactive Extensions for JavaScript(RxJS)について。Linqという名前は付いていませんがLinqの一味と見なせます。いわばLinq to Events, Linq to Asynchronous。ということで、対象となるのはイベントと非同期。ダウンロードは下記URLの右側、Rx for JavaScriptから。

Reactive Extensions for .NET (Rx)

こちらはlinq.jsと違って公式が提供しています。

さて、ではこれは何が出来るのでしょう。出来るのは(Functional) Reactive Programmingです。とは何ぞや。というと、既に親切な解説があるので やさしいFunctional reactive programming(概要編) - maoeのブログ そちらを見るといいと思うな!

とりあえず簡単な例をまず先に。

// マウスの動きの座標ストリーム(無限リスト)
var mousemove = $("#js_advcal_field").toObservable("mousemove")
    .Select(function (e) { return { X: e.pageX, Y: e.pageY} });
 
// 位置をTextに書き出し
mousemove.Subscribe(function (p) { $("#js_advcal_status").text("X=" + p.X + ":Y=" + p.Y) });
 
// 1.5秒遅れて四角形を座標位置に出す
mousemove.Delay(1500)
    .Subscribe(function (p) { $("#js_advcal_rect").css({ left: p.X, top: p.Y }) });
X=0:Y=0

コード似てませんか?今まで出してきたLinq to Objectsのコードに。RxJSは、イベントをコレクションとして扱うことでフィルタ(Where)やマップ(Select)を可能にします。図にするとこうです。

toObservableすることにより、jQueryでアタッチされるMouseMoveイベントは、時間軸上に無限に発生し続ける「無限リスト:Observable Collections」として変換されます。このコレクションの上では時間軸を自由に扱えるため(そもそもMouseMove自体が、いつ次の値が発生するか不確定な時間の上にのっている)、ごくごく自然に、1.5秒後(Delay)という指示を与えるだけで到達時間を遅らせることが出来ます。

RxJSもlinq.jsと同じく、基本的にメソッドの戻り値はObservableオブジェクトとなっていてひたすらメソッドチェーンしていきます。違うのは、linq.jsはToArrayやToString、ForEachなどなど、Enumerable外に出るメソッドが複数ありますが、Observableの場合はSubscribeのみです。SubscribeメソッドがForEachのような役割を担っています。何でToArray出来ないの!というと理由は簡単で、扱う対象が時間軸上に流れる無限リストだからです。無限を有限のArrayに変換は出来ませんよねー。本質的にObservable Collectionsは非同期な状態なので、何か戻り値を返すということは出来ません。出来るのは、向こうからやってくる値に対して実行する処理を示すことだけです。

RxJSは、扱いにくい時間や非同期、複数のイベントが同時に絡む時のイベントの合成を、慣れ親しんだシンプルなコレクション処理のように見た目上落としこんで、foreachするように処理を適用することが出来ます。それが特徴となります。

jQuery

前のコードに出ていた$(”#hoge”)はjQueryです。脈絡なく出してきているわけですね!解説が前後してれぅー。どういうことなのかというと、RxJSは基本的にwindowとは中立です、が、メインで扱う物はDOM(イベント)だったりXmlHttpRequest(非同期)で、これらは抽出の必要があったりクロスブラウザの必要があったりと、一手間二手間な問題を抱えている。それを解決してくれるのがjQueryだったり他のライブラリだったりするわけですね。そこで取られた手段が、jQueryに乗っかること。ようするにプラグイン。RxJSとjQueryを繋ぐ部分を注入。そうして注入されたのがtoObservableというわけです。

linq.jsもjQueryもRxJSも、基本的にメソッドチェーンで自分の世界に閉じっぱなしです。jQueryはモナドだという記事がありましたが、linq.jsもモナドです。RxJSもモナドです。いや、本当に。ただそのへんの理屈は割とどうでもいいわけですが、ただ、交互に変換出来ると便利よねー、なところはあるわけで、三者はプラグインを介して上記の図のような形で遷移可能になっています。

Asynchronous

最後に非同期を。非同期もObservable Collectionsになる、というわけで例から行きましょう。

$("#js_advcal_twbutton").toObservable("click")
    .Do(function () { $("#js_advcal_twitter").empty() })
    .SelectMany(function () { return $.ajaxAsObservable({ url: "http://twitter.com/statuses/public_timeline.json", dataType: "jsonp" }) })
    .SelectMany(function (json) { return Rx.Observable.FromArray(json.data) })
    .Where(function (status) { return status.user.lang == "ja" })
    .Select(function (status) { return $("<p>").text(status.user.screen_name + ":" + status.text) })
    .Subscribe(function (q)
    {
        $("#js_advcal_twitter").append(q);
    });

statuses…

胡散臭い(笑)public_timelineボタンをクリックすると、Twitterのpublic_timelineから日本人のツイート(user.lang==”ja”)のみを表示します。これは、ちょっとメソッドチェーンが多めで微妙にワケワカラン。そんな困ったときはとりあえず図。

起点はボタンのクリックです。これもまたMouseMoveの時と同じで考え方としては無限リスト状。一回目のクリック、二回目のクリック、と無限に続いていきます。このコレクションに対する処理として、次に流れてくるのはDo。これは副作用で、コレクションの値以外の、流れてきたということをトリガーにして外部に影響を与えたい時に使います。今回はクリックをトリガーとして、一旦DIVの中の要素をクリア(empty())しています。

そしてSelectMany。SelectManyはSelectと似ていますが、1:多(だからMany)へと分配するのが特徴です。ここでコレクションの流れは非同期へとバトンタッチされます。非同期のリクエスト、今回はjQueryにおんぶに抱っこでajaxAsObservableにより、twitterのpublic_timeline.jsonからデータを取得。特徴的なのは、非同期なので戻り値が得られるまでに若干のタイムラグがあるわけですが、それは以前に扱ったDelayと同じように、時間軸が少し右に移るだけで、流れ自体はそのままで扱えてしまいます。

1:多ですが、非同期リクエストの戻り値は1なので、見た目上は一個の値が変形しているだけのように見えて、再び次のSelectMany。ここでの値はJSONで、20個分のpublic_timelineのstatusが届いています。それを1:他という形でバラす。RxJS上では「イベント」「非同期」を載せてきましたが、「配列」も問題なく載っかるということです。

ここまで来たら、あとは普通のコレクション処理と同じようにWhereでフィルタリングし(言語が”ja”のみを通す)、Selectで変形し(jQueryで新しいDOMノードを作成)、Subscribeで実行処理を書く(divの中にappend)。

というわけで、「イベント」「非同期」「配列」を一本のコレクションへと合成し、統合しました。一見すると、ただそれだけのことに何でわざわざ複雑めいたことを、と思えてしまいますが、複数の非同期を合成したくなったら?待ち合せたくなったら?などなど、シチュエーションが複雑になればなるほどに、威力を発揮します。

つまりそれJSDeferredで…

です。領域はかなり被ると思います。waitはDelay、nextはSelectまたはSelectManyが相当するでしょう。

// Hello -> 5秒後 -> HelloWorld のalertが出るというもの(UIはフリーズしません)
Rx.Observable.Return("Hello") // パイプラインに"Hello"を流し始める
    .Do(function (x) { alert(x) }) // alertを出す
    .Delay(5000) // 5秒遅延
    .Select(function (x) { return x + "World" }) // 値にWorldを結合
    .Subscribe(function (x) { alert(x) }); // パイプラインの実行開始+alertを出す

例はJSDeferred 紹介より、少し違いますが。また、関数のDeferred化のdeferred.call/failはAsyncSubjectのOnNext/OnErrorが相当しそうです。詳しい話はまたそのうち、もしくはC#で良ければReactive Extensionsの非同期周りの解説と自前実装などを。

まとめ

なげー。スミマセンスミマセン。Enumerable -> コレクション処理 -> 無限リスト -> Observable -> イベントが無限リスト -> 時間軸が不定 -> 非同期 -> コレクション処理。という流れのつもりでした!分量的にEnumerableかObservableか、どっちかに絞るべきでしたね……。もっとあっさり終えるはずだったのにどうしてこうなった。

prototype.jsはRubyっぽい色がある。Firefoxで拡張が続いてるJavaScriptはPythonからの影響が濃ゆい感じ。linq.js/RxJSは勿論C#からで、更にLinqの元はSQLは勿論なのですが、Haskellからの影響も濃く(Linqや、そして現在はRxの開発チームを率いているErik MeijerはHaskellの人で、The Haskell 98 Language Reportにも名前を連ねている)、そうこうして他言語同士が相互に影響を与えてより良くなる。というのはイイ話だなー、って思っていまして。

そして、他言語の文化を受け入れられる懐の広さと、それでもなお自分の色を持ち続けられるJavaScriptってイイ言語だよね、と思います。

と、駄エントリを〆て次のAdvent Calendarにタッチ!

linq.jsやRxJSのベンチマーク

どうも、定期的linq.js - LINQ for JavaScript宣伝の会がやってまいりました。最近はページビューも絶好調、なのだけどDL数はそこまで伸びない(でも同種のライブラリよりもDL数多かったりするので需要が限界値と思われる)などなどな近況ですがこんばんわ。乱立するLinqのJavaScript実装……。などと言うほどに乱立はしてないし、そもそも2009/04に最後発で私が出したのが最後で、それ以降の新顔は見かけないのですが(しいて言えばRxJS)、ちょうどjLinqを実装した人が、ベンチ結果がボロボロだった、作り直してるという記事を出したので、ほぅほぅとそのベンチマークを見て、ちょっと改良して色々なLinq実装で比較してみました。

jOrderのベンチに色々足したもの

左のがIE8、重ねて後ろ側のがChrome。この画像は77件のJSONをGroupIDが107か185のもののみをフィルタして配列を返すという処理を1000回試行したもの。毎度思いますが、V8恐ろしく速い。そりゃnode.jsとか普通に現実的な話ですよね、大変素晴らしい。

jOrderについて

このベンチマークは、もとはjOrderという、Linq……ではなくてSQL風のもので(SQLっぽいのは結構いっぱいあります)、巨大なJSONを効率よく抽出するために、先にインデックス的なのを作ってそれから処理すれば速くなるよ!というライブラリが先々月ぐらいに出来たばっからしいのですが、それがjLinqと比較してこれだけ速いぜ!とやっていたようです。結果見る限りはjLinqクソ遅くてjOrderクソ速くて凄ー、となったのですが、なんかどーにも胡散臭さが拭えないわけですよ、ベンチ詐欺に片足突っ込んでいるというか。

jOrderは初回にインデックスっぽいものを作成するので、二回目以降の抽出は爆速、というのがウリ(っぽい)ようで、ベンチは確かに速い。で、その初回のインデックス生成は何時やってるんでしょうか?このベンチのソースを見ると、ボタンを押してからじゃなくて、ページのロード時にやってますね……。あの、それも立派なコストなのですが、無視ですか?無視ですか?そりゃあ試行回数を1000でベンチ取るならば無視出来るほどに小さいかもですね?でも、Test Cycles 1とか用意しているわけですが、どうなんでしょうね、インデックス作成時間を無視するのは、ちょっと卑怯すぎやしませんか?そもそも対象にひぢょーに遅いjLinq「だけ」を選んでいるというところがやらしい。

というわけで、オリジナルのベンチにはないのですがwith create indexというボタン押してからインデックスを作成する項目を足しました。1000回の試行では、コンセプトに乗っ取るなら1回のインデックス作成にすべきなんでしょうが、普通に1000回インデックス作成に走るのでクソ遅いです。あ、いや、別にアンチキャンペーン張ろうってわけじゃあないんですが、単純に面倒なので……。インデックス作成コストは試行回数1にすれば分かる。

ベンチ結果を見ると、まず、インデックス的なものの作成には非常にコストがかかってる。そして、わざわざコストをかけて生成したところで、Small table(77件のJSON)では、フィルタリングに関してはjQueryの$.grep、つまりは何も手をかけてないシンプルなフィルタリングと同じ速度でしかなくて、あまり意味が無い。Large table(1000件のJSON)ではそれなりな効果が出ているようですが、インデックス作成コストをペイするまでの試行回数を考えると、やはりあまり意味がなさそうな……。コンセプトは面白いんですが、それ止まりかなあ。機能的には、このインデックス生成一点勝負なところがあるので、他のLinq系ライブラリのような多機能なクエリ手段があるわけでもないし。

その他のライブラリについて

どれも似たり寄ったりで同じことが出来ますが、処理内容は全然違います。linq.jsは遅延評価であることと、列挙終了時にDisposeすることを中心に据えているので、シンプルにフィルタするだけのもの(jQueryの$.grepとか)よりも遥かに遅くなっています。JSINQも同じく遅延評価で、実装も大体似てます。なので、計測結果もほぼ同じですが、linq.jsのほうが遅い。これは、jsinqはDisposeがないため、その分の速度差が出ています(それ以外にも、単純にlinq.jsのほうが色々処理挟んでて遅め)。

LINQ to JavaScript(JSLINQ)はLINQの名を冠していますが、即時評価で、中身はただの配列のラッパーです。その分だけ単純な実装になっているので、単純なことをこなすには速い。jQueryの$.grepも同じく、普通に配列をグルッとループ回してifで弾いて、新しい配列にpushして、新しい配列を返すもの。というわけで、両者はほとんど同じ速度です。ただ、若干jQueryのほうが速いようで。これは、JSLINQはthis.itemsという形で対象の配列にアクセスしていて、それが速度差になってる模様。var items = this.itemsと列挙の前に置いてやれば、jQueryとほぼ同じ速度になる。1000回の試行だと20msecぐらいの差にはなるようですね。これが気にするほどかは、どうでしょう……。私は全く気にしません。

残念なことにめっちゃ遅いjLinqは、うーん、中はevalだらけだそうで、それが響いたそうです。と、作者が言ってるのでそうなのでしょう(適当)。RxJSも割と遅いんですが、これはしょうがないね!C#でもToObservableで変換かけたものの速度は割と遅くなるし。構造的に中間にいっぱい処理が入るので、そういうものだということで。

速度ねえ……

jLinqはさすがにアレゲなのですが、それ以外は別に普通に使う範囲ではそんな致命的に低速ってわけでもないんで、あまり気にしなくても良くね?と、かなり思ってます。linq.jsは速度を犠牲にして遅延評価だのDisposeだの入れてるわけですが、勿論、犠牲にしたなりのメリットはある(表現できる幅がとっても広がる)し。その辺はトレードオフ。配列をSelectしてToArrayするだけ、とかWhereしてToArrayするだけならば、、どうせjQueryも一緒に使うでしょ?的に考えて、jQueryの$.map, $.grepを使えば精神衛生上良いかもしれません。これは、C#で言うところのArray.ConvertAllは化石メソッドだけど、SelectしてToArrayならばConvertAllのほうが高効率なんだぜ(内心はどうでもいーんだけど)、といったようなノリで補えば良いでしょう。

それにしても、何でjQueryは$.eachの引数がmapやgrepと逆(eachだけindexが第一引数で値が第二引数)なんですかね。これ、統一してたほうが良いし、だいたいがして値が第一引数のほうが使いやすいのに。もう今更変えられない、ということなのかしらん。

そういえばで、せっかくなので「表現できる幅」の例として、ベンチには第一ソートキーにCurrency、それが重複してた場合の第二ソートキーにTotalを指定してみた例(OrderBy.ThenBy)とか(linq.js無しで書くとちょびっと面倒だよ!)、GroupIDでグルーピングした後にTotal値を合計といった集計演算(これもlinq.js無しだと面倒だよ!)とかを入れておいたので、良ければ見といてください。はい。まあ、別にこの辺はeager evaluationでも出来るというかソートもグルーピングも一度バッファに貯めちゃってるんですけどね!

まとめ

JSINQは良く出来てると思うのよ。ほんと(私はただのLinqマニアなので、基本的に他の実装は割と読んでますですよ)。ベンチ的にもlinq.jsより速いし(Disposeないからね、でもDispose使うシーンがそもそもあんまないという)、文字列クエリ式も(使わないけど)使えるし。じゃあ、JSINQじゃなくてlinq.jsがイイ!というような押しは、そこまであるかないか、どうなんでしょうね。1.メソッドの数が全然違う 2.ラムダ式的な文字列セレクターが使える 3.Dispose対応 4.RxJSにも対応 5.jQueryにも対応 6.WSHにも対応 7.VS用IntelliSense完備。ふむ、結構ありますね。というわけでlinq.jsお薦め。冒頭でも言いましたが最近のCodePlex上でのページビュー/ダウンロード数を見ると、競合のlinq移植ライブラリの中でもトップなんですよ、えへへ。まあ、4DL/dayとかいうショボい戦いなのですが。

jLinqの人が、パフォーマンス改善のついでにLinqという名前をやめてブランディングやり直すって言ってますが、きっと正しいと思う。「Linq」という名前がつく限りは「.NETの~」という印象が避けられないし、そのせいで敬遠されるというのは、間違いなくある。jLinqは、中身全然Linqじゃない独特な感じのなので、名前変えるのは、きっと良い選択。

linq.jsは100% Linqなので名前がどうこうってのはないですが、しかし、RxJSもそうなのだけど、.NET以外の人にも使って欲しいなって気持ちはとてもあります。やれる限りは頑張ってるつもりなんですが、中々どうして。JavaScriptエディタとしてのVisual Studioの使い方入門は100ブクマまであとちょい!な感じで、そういうとこに混ぜて宣伝とかいうセコい策を取ってはいるものの(いや、別にそういうつもりでやったわけでもないですが)色々と難すぃー。海外へも少しは知名度伸ばせたようなのだけど、そこでも基本的には.NET圏のみって雰囲気で、どうしたものかしらん。

つまるところ、そろそろ御託はどうでもいいから、RealWorldな実例出せよって話ですね!

linq.js ver.2.2.0.0 - 配列最適化, QUnitテスト, RxJSバインディング

CodePlex - linq.js - LINQ for JavaScript

linq.jsをver 2.2に更新しました。変更事項は、メソッドの追加、配列ラッピング時の動作最適化、ユニットテストのQUnitへの移行、RxJSバインディング追加の4つです(あと、若干のバグフィックスと、RxJS用vsdoc生成プログラムの同梱)。まずは、追加した二つのメソッドについて。

var seq = Enumerable.From([1, 5, 10, 4, 3, 2, 99]);
 
// TakeFromLastは末尾からn個の値を取得する
var r1 = seq.TakeFromLast(3).ToArray(); // [3, 2, 99]
// 2.0から追加されているTakeExceptLast(末尾からn個を除く)と対になっています
var r2 = seq.TakeExceptLast(3).ToArray(); // [1, 5, 10, 4]
 
// ToJSONはjson文字列化します(列挙をJSON化なので必ず配列の形になります)
// JSON.stringifyによるJSON化のため、
// ネイティブJSON対応ブラウザ(IE8以降, Firefox, Chrome, Opera...)
// もしくはjson2.jsをインポートしていないと動作しません
var objs = [{ hoge: "huga" }, { tako: 3}];
var json = Enumerable.From(objs).ToJSON(); // [{"hoge":"huga"},{"tako":3}]

TakeFromLast/TakeExceptLastはRxからの移植です(Rxについては後でまた少し書きます)。RxではTakeLast, SkipLastという名前ですが、諸般の都合により名前は異なります。より説明的なので悪くはないかな、と。

もう一つはToJSONの復活。ver 1.xにはあったのですが、2.xでばっさり削ってたました。復活といっても、実装は大きく違います。1.xでは自前でシリアライズしていたのですが、今回はJSON.stringifyに丸投げしています。と、いうのも、IE8やそれ以外のブラウザはJSONのネイティブ実装があるので、それに投げた方が速いし安全。ネイティブ実装はjson2.jsと互換性があるので、IE6とかネイティブ実装に対応していないブラウザに対しては、json2.jsを読み込んでおくことでToJSONは動作します。

json2.jsはネイティブ実装がある場合は上書きせずネイティブ実装を優先するようになっているので、JSON使う場合は何も考えずとりあえず読み込んでおくといいですね。

配列ラップ時の最適化

Any, Count, ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Skip, SequenceEqual, TakeExceptLast, TakeFromLast, Reverse, ToString。

Enumerable.From(array)の直後に、以上のメソッドを呼んだ際は最適化された挙動を取るように変更しました。各メソッドに共通するのは、lengthが使えるメソッドということです。Linqは基本的に長さの情報を持っていない(無限リストとか扱えるから)ため、例えばCountだったら最後まで列挙して長さを取っていました。しかし、lengthが分かっているのならば、Countはlengthに置き換えられるし、Reverseは[length - 1]から逆順に列挙すればいい。ElementAt(n)はまんま[n]だしLastは[length - 1]だし、などなど、lengthを使うことで計算量が大幅に低減されます。

C#でも同様のことをやっている(ということは以前にLinqとCountの効率という記事で書いてあったりはする)のですが、今になってようやく再現。先の記事にあるように、C#では中でisやasを使ってIEnumerableの型を調べて分岐させてますが、linq.jsでは Enumerable.FromでEnumerableを生成する際に、今まではEnumerableを返していたところを、ArrayEnumerable(継承して配列用にメソッドをオーバーライドしたもの)を返す、という形を取っています。

これが嬉しいかどうかというと、そこまで気にするほどではありません。C#では array.Last() のように使えますが、linq.jsではわざわざ Enumerable.From(array).Last() と、ラップしなきゃいけませんから、それならarray[array.length - 1]でいいよ、という。ちなみに当然ですがFrom(array).Where().Count()とか、他のLinqメソッドを挟むと、ArrayEnumerableじゃなくEnumerableになるため最適化的なものは消滅します。

でもまあ、意味はあるといえばあります。配列を包んだだけのEnumerableは割と色々なところで出てきます。例えばGroupJoin。これのresultSelectorの引数のEnumerableは、配列をラップしただけです。又は、ToLookup。Lookupを生成後、Getで取得した際の戻り値のEnumerableは配列を包んだだけです。GroupByの列挙(Grouping)もそう。特にGroupingで、グループの個数を使うってシーンは多いように思います。そこで今まではCount()で全件列挙が廻っていたのが、一度も列挙せずに値が取れるというのは精神衛生上喜ばしい。

パフォーマンス?

このArrayへの最適化は勿論パフォーマンスのためなのですが、じゃあ全体的にlinq.jsのパフォーマンスはどうなの?というと、遅いよ!少し列挙するだけで山のように関数呼び出しが間に入りますから、速そうな要素が一つもない。ただ、遅さがクリティカルに影響するほどのものかは、場合によりけりなので分かりません。遅い遅いと言っても、jQueryでセレクタ使って抽出してDOM弄りするのとどちらが重いかといったら、(データ量にもよりますが)圧倒的にDOM弄りですよね?的な。

JavaScriptは、どうでもいいようなレベルの高速化記事がはてブなんかにも良く上がってくるんですが、つまらない目先に囚われず、全体を見てボトルネックをしっかり掴んでそこを直すべきだと思うんですよね。「1万回の要素追加で9msの高速化」とか、意味無いだろそれ絶対と思うのですが……。

ただ、アプリケーションとライブラリだと話は別で、ライブラリならば1msでも速いにこしたことはないのは事実です。linq.jsは仕組み的には遅いの確定なのはしょうがないとしても、もう少しぐらいは、速度に気を使って努力すべきな気はとてもします。今後の課題。

コードスニペット

無名関数書くのに、毎回function(x) { return って書くの、面倒くさいですよね。ということで、Visual Studio用のコードスニペットを同梱しました。func1->Tab->Tabで、linq.jsで頻繁に使う一行の無名関数 function(x){ return /* キャレットここ */ } を生成してくれます。これは激しく便利で、C#の快適さの3割ぐらいを占めていると言っても過言ではないぐらいに便利なのですが、動画じゃないと伝わらないー、けれど動画撮ってる体力的余裕がないので省略。

func0, func1, func2, action0, action1, action2を定義しています。0だの1だのは引数の数。funcはreturn付き、actionはreturn無しのスニペットです。また、Enumerable.RangeとEnumerable.Fromにもスニペットを用意しました。erange, efromで展開されます。jQueryプラグイン版の場合はjqrange, jqfromになります。

インストールは、Visual Studio 2010でツール→コードスニペットマネージャーを開いてインポートでsnipetts/.snippetを全部インポート。

binding for RxJS

RxJS -Reactive Extensions for JavaScriptと接続出来るようになりました。ToObservableとToEnumerableです(jQuery版のTojQueryとtoEnumerableと同じ感覚)。

// enumerable sequence to observable
var source = Enumerable.Range(1, 10)
    .Shuffle()
    .ToObservable()
    .Publish();
 
source.Where(function (x) { return x % 2 == 0 })
    .Subscribe(function (x) { document.writeln("Even:" + x + "<br>") });
 
source.Where(function (x) { return x % 2 != 0 })
    .Subscribe(function (x) { document.writeln("Odd:" + x + "<br>") });
 
source.Connect();
 
// observable to enumerable
var subject = new Rx.ReplaySubject();
 
subject.OnNext("I");
subject.OnNext(4);
subject.OnNext("B");
subject.OnNext(2);
subject.OnNext("M");
 
var result = subject.ToEnumerable()
    .OfType(String)
    .Select(function (x) { return x.charCodeAt() - 1 })
    .Select(function (x) { return String.fromCharCode(x) })
    .ToString("-");
 
alert(result); // H-A-L

ToObservableでは、こないだ例に出したPublishによる分配を。ToEnumerableは、例が全然浮かばなかったので適当にOnNextを発火させた奴をEnumerable化出来ますねー、と。なお、cold限定です。hotに対して適用すると空シーケンスが返ってくるだけです(ちなみにC#版のRxでhotに対してToEnumerableするとスレッドをロックして無限待機になる)

それと、RxVSDocGeneratorの最新版を同梱してあります。以前に公開していたのは、RxJSのバージョンアップと同時に動かなくなっちゃってたのよね。というわけで、修正したうえで同梱商法してみました。

プレースホルダの拡張

無名関数のプレースホルダが少し拡張されました。今までは引数が一つの時のみ$が使えたのですが、今回から二引数目は$$、三引数目は$$$、四引数目は$$$$が使えるようになりました。

// 連続した重複がある場合最初の値だけを取る
// (RxのDistinctUntilChangedをlinq.jsでやる場合)
// 1, 5, 4, 3, 4
Enumerable.From([1, 1, 5, 4, 4, 3, 4, 4])
    .PartitionBy("", "", "key,group=>group.First()")
// $$でニ引数目も指定出来るようになった
Enumerable.From([1, 1, 5, 4, 4, 3, 4, 4])
    .PartitionBy("", "", "$$.First()")

便利といえば便利ですが、あまりやりすぎると見た目がヤバくなるので適度に抑えながらでどうぞ。なお、以前からある機能ですが”"は”x=>x”の省略形です。PartitionByでは、それぞれkeySelectorとelementSelector。

入門QUnit

今までlinq.jsのユニットテストはJSUnitを使用していたんですが、相当使いにくくてやってられなかったため、QUnitに移しました。QUnitはjQueryの作者、John Resigの作成したテストフレームワークで、流石としか言いようがない出来です。物凄く書きやすい。JSUnitだとテストが書きづらくて、だから苦痛でしかなかった。テストドリブンとか言うなら、まずはテストが書きやすい環境じゃないとダメだ。

JSUnitのダメな点―― 導入が非常に面倒。大量のファイルを抱えたテスト実行環境が必要だし、クエリストリングでファイル名を渡さなければならなかったり、しかも素ではFirefox3で動かなかったりと(Firefox側のオプションを調整)下準備が大変。面倒くささには面倒くささなりのメリット(Java系の開発環境との連携とかあるらしいけど知らない)があるようですが、俺はただテスト書いて実行したいだけなんだよ!というには些か重たすぎる。一方、QUnitはCSSとJSとHTMLだけで済む。

また、JSUnitはアサーションのメソッドが微妙。大量にあるんだけど、逆に何が何だか分からない。assertObjectEqualsとかassertArrayEqualsとか。ArrayEqualsはオブジェクトの配列を値比較してくれない上に、それならせめて失敗してくれればいいものの成功として出されるから役に立たなかったり、ね……。QUnitは基本、equal(参照比較)とdeepEqual(値比較)だけという分かりやすさ。deepEqualはしっかりオブジェクト/配列をバラして再帰的に比較してくれるという信頼感があります。

テスト結果画面の分かりやすさもQUnitに軍配が上がる。というかJSUnitは致命的に分かりづらい。一つのテスト関数の中に複数のアサートを入れると、どれが失敗したか分からないという有様。なのでJSUnitではtestHoge1, testHoge2といった形にせざるを得ないのだけど、大変面倒。更に、JSUnitのテスト実行は遅くて数百件あるとイライラする。

そもそもJSUnitはコードベースが古いし最近更新されてるかも微妙(GitHubに移って開発は進んでるようですが)。というわけで今からJavaScriptでユニットテストやるならQUnitがいいよ!残念ながらか、ネットを見ると古い紹介記事ばかりが見当たるので、ていうかオフィシャルのドキュメントまで古かったりしてアレゲなので、簡単に解説します。と、思ってたのですが、一月程前に素晴らしいQUnitの記事が出ていました。なので基本無用なのですが、文章を書いてしまってあったので(これ書いてたのは4月頃なのです)出します、とほほ。

まず、QUnit - jQuery JavaScript LibraryのUsing QUnitのところにあるqunit.jsとqunit.cssを落とし、下記のテンプレHTMLは自前で作る。ファイル名はなんでもいいんですが、私はtestrunner.htmとでもしておきました。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>linq.js test</title>
    <link href="qunit.css" rel="stylesheet" type="text/css" />
    <script src="qunit.js" type="text/javascript"></script>
    <!-- テストに必要な外部ライブラリは好きに読み込む -->
    <script src="linq.js" type="text/javascript"></script>
    <!-- ここにテスト直書きもアリだし -->
    <script type="text/javascript">
        test("Range", function()
        {
            deepEqual(Enumerable.Range(1, 3).ToArray(), [1, 2, 3]);
        });
        // 自動的にロード後に実行されるので、これも問題なく動く
        test("hoge", function ()
        {
            var h2 = document.getElementsByTagName("h2");
            equal(h2.length, 2);
        });
    </script>
    <!-- 外部jsファイルにして読み込むのもアリ -->
    <script src="testEnumerable.js" type="text/javascript"></script>
    <script src="testProjection.js" type="text/javascript"></script>
</head>
<body>
    <h1 id="qunit-header">linq.js test</h1>
    <h2 id="qunit-banner"></h2>
    <h2 id="qunit-userAgent"></h2>
    <ol id="qunit-tests"></ol>
</body>
</html>

実行用のHTMLにqunit.jsとqunit.cssを読み込み、body以下の4行を記述すれば準備は完了(bodyの4行はid決め打ちで面倒だし、どうせ空なので、qunit.js側で動的に生成してくれてもいいような気がする、というか昔はそうだった気がするけどjQuery依存をなくした際になくしたのかしらん)。

あとは、test(”テスト名”, 実行される関数) を書いていけばいいだけ。そうそう、test関数はHTMLが全てロードされてから実行が始まるので、jQuery読み込んでjQuery.readyで囲む必要とかは特にありません。testProjection.jsは大体↓のような感じ。

/// <reference path="testrunner.htm"/>
 
module("Projection");
 
test("Select", function ()
{
    actual = Enumerable.Range(1, 10).Select("i=>i*10").ToArray();
    deepEqual(actual, [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);
    actual = Enumerable.Range(1, 10).Select("i,index=>i*10+index").ToArray();
    deepEqual(actual,[10, 21, 32, 43, 54, 65, 76, 87, 98, 109]);
});

reference pathはVisualStudio用のパスなのであんま気にせずにー。詳細はJavaScriptエディタとしてのVisual Studioの使い方入門のほうで。読み込み元のHTMLを指定しておくとIntelliSenseが効いて、書くのが楽になります。

actual(実行結果)は私は別変数で受けてますが、当然、直書きでも構いません。アサーション関数は、基本は(actual, expected(期待する結果), message(省略可能)) の順番になっています。

equal(1, "1"); // okay - 参照比較(==)
notEqual
strictEqual(1, "1"); // failed - 厳密な比較(===)
notStrictEqual
deepEqual([1], [1]); // okay - 値比較
notDeepEqual
ok(1 == "1"); // okay - boolean
ok(1 !== "1");  // okay - notの場合は!で

基本的に使う関数はこれらだけです。ドキュメントへの記載はないのですが、以前にあったequalsとsameはequalとdeepEqualに置き換わっています。後方互換性のためにequals/sameは残っていますが、notと対称が取れるという点で、equal/deepEqualを使ったほうが良いんじゃないかと思います。

非同期テストとかは、またそのうちに。

まとめ

linq.js ver.2出したときには、もう当分更新することなんてないよなあ、なんて思っていたのですが、普通にポコポコと見つかったり。でもさすがに、もうないと思いたい。C#との挙動互換性も、私の知る限りでは今回の配列最適化が最後で、やり残しはない。そして今回がラストだー、とばかりに思いつく要素を全部突っ込んでやりました。

そんなわけなので、使ってやってください。私がVisualStudio使いなのでVS関連の補助が多めですが、別にVS必須というわけじゃなくプラスアルファ的なもの(入力補完ドキュメントだのコードスニペットだの)でしかないので、エディタ書きでも何ら問題なく使える、かな、きっと。

JavaScriptエディタとしてのVisual Studioの使い方入門

linq.jsってデバッグしにくいかも……。いや、やり方が分かればむしろやりやすいぐらい。という解説を動画で。HDなので文字が見えない場合はフルスクリーンなどなどでどうぞ。中身の見えないEnumerableは、デバッガで止めてウォッチウィンドウでToArrayすれば見えます。ウォッチウィンドウ内でメソッドチェーンを繋げて表示出来るというのは、ループが抽象化されているLinqならではの利点。sortしようが何しようが、immutableなので元シーケンスに影響を与えません。ラムダ式もどきでインタラクティブに条件を変えて確認出来たりするのも楽ちん。

ところで、JavaScript開発でもIDE無しは考えられません。デバッグというだけならFirebugもアリではありますが、入力補完や整形が可能な高機能エディタと密接に結びついている、という点でIDEに軍配があがるんじゃないかと私は思っています。動画中ではVisual Studioの無料版、Visual Web Developerを使っています。Visual Studioというと、何か敷居が高く感じられるかもしれませんが、使う部分を絞ってみれば、超高性能なHTML/JavaScriptエディタとして使えちゃいます。有料版の最高級エディションは170万円ですからね(MSDNという何でも使えるライセンスがセットなので比較は不公平ですが)、機能限定版とはいえ、その実力は推して知るべし、です(機能限定部分は、主にC#でのASP.NET開発部分に絡むものなのでJavaScript周りでは全く関係ありません)。

VSを使うと何が嬉しいのでしょう?JavaScriptでの強力な入力補完、自動整形、使いやすいデバッガ、リアルタイムエラー通知。そしてこっそり地味に大切なことですが、jQueryの完璧な日本語ドキュメント付き入力補完が同梱されています。と、嬉しいことはいっぱいあるのですが、ASP.NETの開発用ではあるので、JS開発には不要なメニューが多くて戸惑う部分も多いのは事実。分かれば不要部分はスルーするだけなので簡単なのですが、そこまでが大変かもしれない。なので、JavaScript開発で使うVisualStudio、という観点に絞って、何が必要で不要なのかを解説していきます。

インストール

何はともあれまずはインストール。Microsoft Visual Studio ExpressからVisual Web Developerを選び、リンク先のWeb Platform Installerとかいうのをダウンロード&実行。

PHPとかWordPressとか色々ありますがどうでもいいので、Visual Web Developer 2010 Expressだけ入れましょう。クリックして指示に従って適当に待つだけ、10分ぐらいあれば終わるはず。10分は短くはないですが、インストール自体は非常に簡単です。

プロジェクト作成

実行すると初回起動時はイニシャライズが若干長いですが、それを超えれば新しいプロジェクトと新しいWebサイトの違いが分からねえええええ。で、ここは新しいWebサイトです。プロジェクトのほうはC#でASP.NETが基本なので関係ありません。スタートページから、もしくはファイル→新規作成→Webサイト。

更に項目があって分からねえ、けどここはASP.NET空のウェブサイトを選びます。次にソリューションエクスプローラーウィンドウを見ます(なければ表示→ソリューションエクスプローラー)。web.configとかいうゴミがありますが、それはスルーしておきましょう(消してもいいですが復活します)。空なので、ルートを右クリックして新しい項目の追加。

いっぱいあると思いますが、ほとんど関係ありません、ノイズです。真ん中ぐらいにあるHTMLページかJScriptファイルを選びましょう。あとは、エディタでガリガリと書いたら、Ctrl+F5を押せば簡易サーバーが立ち上がり、ブラウザ上に現在編集中のHTMLが表示されます。

以上が基本です。手順は簡単なので一度覚えればすんなり行くはずです。最初は如何せんHTML/JS用としてはダミー項目が多いのがやや難点。なお、保存時はデフォルトではMy DocumentのVS2010のWebSites下にHTMLとかが、Projects下に.slnファイル(プロジェクトを束ねている設定とかが書かれたファイル)が置かれています。以後プロジェクトをVSで開くときは.slnのほうをダブルクリック、もしくはスタートページの最近使ったプロジェクトから。

では、Visual Studioを使ってJavaScriptを書いて嬉しい!機能を幾つか挙げていきます。

エラー表示

小括弧が、波括弧が、足らなかったり足しすぎだったりを見落とすことは割とあります。そして起こる実行時エラー。こんなのコンパイルエラーで弾かれてくれ、あばばばば。と思うときはいっぱいあります。そこでVisual Studioのリアルタイムエラー検出。

hoge = functionではなくhoge : function。下のは波括弧が一個多い。というのを、リアルタイムで検出してくれて、疑わしいところには波線を敷いてくれます。エラー一覧にも表示されるので、このウィンドウは常時表示させておくと書くのが楽になります。私は縦置きにしてエディタの左側にサイドバーとして常時表示。カラムはカテゴリと説明だけにしています。

エラー通知のためのコード走査はバックグラウンドで定期的に動いているようですが、任意に発動させたい場合はCtrl + Shift + Jで行えます。修正結果が正しいのかとっとと確認したいんだよ馬鹿やろー、って時に便利。というか普通に押しまくります、私は。

コードフォーマット

コード整形は大事な機能だと思っています。手動でスペース入れていくとか面倒くさいし。かといって整形が汚いコードは萎えます。

ショートカットはCtrl+K、で、Ctrlを押しながら続けてD。微妙に覚えにくいショートカット。ちなみに選択範囲のコメント化はCtrl+K, Cで、非コメント化はCtrl+K, U。ようするに整形系はCtrl+K始まりで、DはDocumentFormat、CはComment、UはUncommentの意味になるようです。フォーマットのルール(改行をどこに入れるか、とか)は設定で変えられます。

デバッグ

当然のようにブレークポイントの設定、ステップイン、ステップアウトなどのデバッグをサポートしています。

F9でブレークポイントを設定してF5でデバッグ実行。が基本です。ローカルウィンドウで変数の値表示、そして便利なのがウォッチウィンドウで、見たい値を好きに記述出来ます。式も書けるので平気で副作用かませます。で、デバッガで良いのはthisが見れるところですねー。JavaScriptはthisが不定で、いったいこの中のthisは何を指しているんだ!と悩んでしまうわけですが、そんなものデバッガで見れば一発で分かりますね、はは。考えるより前にとりあえずデバッグ実行。

さて、そんなデバッグですが、初回時には何やら怪しげなダイアログが上がります。ここはYESで。そして、デバッグ出来ましたか?出来なかった人も多いかもしれません。実は、IEじゃないとデバッガ動かないのです。というわけで、ソリューションエクスプローラーからプロジェクトのルート部分を右クリックしてブラウザの選択を選ぶ。

IEをデフォルトにしてください。一度設定すれば、以降はこの設定が継続されます。IEとか冗談じゃない。と思うかもしれませんが、えーと、IEで常に書くことで、IEで動かないスクリプトを書くことを避けられるのです、とかいうどうでもいい効用はあります。でもまあ、Firefox拡張とかChrome拡張を書くのにはデバッガが使えなくなるも同然なのは不便ですね。その時はデバッグは当然ブラウザ固有のデバッガを使い(デバッガを使わないと言う選択肢はないよ!)、エディタとしてだけに使えばいいぢゃない。

入力補完/日本語jQuery

入力補完(IntelliSense)は素敵。ローカル変数があればそれが出てくる。もう変数名打ち間違えで動かない、とかない。ドットを打てば、補完候補に文字列であればreplaceとか、配列であればjoinとか、DOMであればappendChildとか出てくる。メソッド名を暗記する必要もなければ、打ち間違えることもない。

補完は割と賢くて、関数では引数を見て(というか裏でインタプリタ走ってるんですね、きっと)、ちゃんと返す値を判別してくれます。

ところでですが、最初の釣り画像にあるjQueryの日本語化ドキュメントはどこにあるのでしょうか?

ファイル→新規作成→プロジェクトからASP.NET Webアプリケーションを選びます。すると、Scriptsフォルダの下にjquery-1.4.1-vsdoc.jsとかいうものが!こいつを、コピペって頂いてしまいましょう。ASP.NET Web Application自体はどうでもいいので破棄です、破棄。でもせっかくなので、Default.aspxを開いてCtrl+F5で実行してみてください。出来た!ウェブアプリが出来た!そう、C#+ASP.NETは驚くほど簡単にウェブアプリが作れるんです。あとは安レンタルサーバーさえ普及してくれれば……。

vsdocについて

-vsdoc自体は<script src>で読み込む必要はありませんし、実際にサーバーにアップロードする必要もありません。仕組みとしてはhoge.jsとhoge-vsdoc.jsが同じ階層にあると、VisualStudioの入力補完解析はhoge-vsdoc.jsを見に行く、といった感じになっています。なので、jquery-1.4.1.jsだけを読み込めばOKです。

HTMLファイルに記述する場合はscript srcで読み込めて補完が効くのは分かるけど、単独JSファイルの場合は読み込みの依存関係をどう指定すればよいでしょうか。答えは、ファイルの先頭にreference pathを記載します。

これで、JScript1.jsという単独JSファイルでもjQueryの補完が効かせられるようになりました。reference pathというのはVSだけで効果のあるタグで、ブラウザの解釈上はコメントに過ぎないので、ブラウザ表示時に問題が出ることもありません。

なお、このreference pathというのを覚えている必要はありません。refと記述してTabを二回押すとこのタグが展開されるはずです。コードスニペットというコード挿入の仕組みに予め用意されているわけです。なお、コードスニペットは、この他にもfor->Tab x2でforが展開されたりなど色々あって便利です(自分で作成することも出来る)。

その他設定など

その他、好みもありますが設定など。ツール→オプションから。

何はともかくフォントの変更。MSゴシックとかありえん。フォントをConsolasにしましょう! Consolasはプログラミング用のClearTypeに最適化された見やすい素敵フォントです。勿論、スラッシュドゼロ。サイズは私は9で使ってます。

Ctrl+F5押す度にアウトプットウィンドウが立ち上がるのが猛烈にウザいので、「ビルド開始時に出力ウィンドウを表示」のチェックは外しておく。

HTMLでの属性の引用符自動挿入はチェックつけといたほうが幸せ気分。

入力候補の、このTabかEnterのみで確定させるってのはチェックを外す。だってメソッド書くときは「(」で確定させたいし、オブジェクトを開くときは「.」で確定させたいもの。例えばdocument.getElementByIdは「doc -> Enter -> . -> get -> Enter -> (」じゃなくて「doc -> . -> get -> (」というように、スムーズに入力したい。一々Enterを挟むのは流れを止めてしまう。

まとめ

IDEを知ってて使わない、というのは個人の好き好きなのですが、単純に知らないというのは勿体無いな、と。特に初心者ほどIDEを必要とすると思います。初心者がプログラミング始めるなら、導入がメモ帳とブラウザだけで開発出来るJavaScriptお薦め!って台詞は、あまりよろしくないんじゃないかなー。初心者ほど些細なスペルミスや構文ミスでつまづく上に、目を皿のようにしてみても原因が分からない。たとえ導入までの敷居が若干高くなろうとも、親切にエラー箇所に波線を敷いてくれるIDEこそ必要なんじゃないかな。あと、デバッガ。ビジュアルに変数が動き変わることほど分かりやすいものもないでしょう。

IDEもEclipseのプラグインとか色々ありますが、Visual Studioの強力なjQuery対応度は何にも代え難いんじゃないでしょうか。導入もオールインワンなので何も考えなくてもいい簡単さですし。是非一度、試してみてもらえればいいなあ。

ついでですが、冒頭動画のlinq.jsは便利なJavaScriptライブラリ(無名関数を多用して関数型言語的にコレクション操作を可能にする)でいて、更にVisual Studioの入力補完に最適化してあるので使ってみてください、と宣伝。いや、作者私なので。ごほごほ。jQueryプラグインとして動作するバージョンも同梱してあります。

それと、勿論Visual Studioは有料版のほうが高機能な面もあります。JavaScript開発のみだとあまり差はないのですが、WindowsScriptHostをJavaScriptで書いてもデバッグ出来るとか無料版に比べて大したことない利点があるにはあります。C#でSilverlightなどもごりごり書きたい、とかになれば断然、有料版のほうが輝いてきます。

Ultimateは100万オーバーで無理なので、Professional買いましょう、私は買います。(メインはC#の人間なので。JSの人は正直Expressでイイと思うよ……)。まだ発売されてないのでこれから買います。「アップグレード」ですが、Express(無料版)からのアップグレードも認められているという意味不明仕様なので(誰が倍額する通常版買うんでしょうかね……)皆様も是非、上のリンクからamazonで買ってくれれば、ごほごほ。

linq.js ver 2.1.0.0 - ToDictionary, Share, Let, MemoizeAll

CodePlex - linq.js - LINQ for JavaScript

linq.jsを2.0から2.1に更新しました。今回はただのメソッド追加というだけじゃなく、1.*の時から続いてる微妙コードを完全抹殺の一掃で書き換えた結果、内部的にはかなり大きな変更が入りました。その影響で挙動が変わってるところも割とあります。

まず、OrderByのロジックを変更しました。これで動作はC#完全準拠です(多分)。前のはかなりアレだったのでずっと書きなおしたいと思ってたのですが、やっと果たせました。従来と比べるとThenByを複数個繋げた時の挙動が変わってくる(今まではOrderByのみ安定ソートで、ThenBy以降は非安定ソートだったのが、今回からは幾つ繋げても安定ソートになります)ので、複雑なソートをやろうとしていた場合は違う結果が出る可能性はなきにしもあらず、ですが、基本的には変化なしと見て良いと思います。

ちなみにJavaScriptのArray.sortは破壊的だし、安定である保証もないので、linq.jsのOrderBy使うのは素敵な選択だと思いますよ!非破壊的で安定で並び替え項目を簡単に複数連結出来る(ThenBy)という、実に強力な機能を提供しています。代償は、ちょっと処理効率は重いかもですね、例によってそういうのは気にしたら負けだと思っている。

他に関係あるところで大きな変更はToLookup。戻り値を、今まではJSのオブジェクトだったのですが、今回からはlinq.jsの独自クラスのLookupになります。すみませんが、破壊的変更です、前と互換性ありません。変えた理由はJSのオブジェクトを使うとキーが文字列以外使えないため。そのことはわかっていて、でもまあいっかー、と思っていたのですがGroupByとか内部でToLookupを使ってるメソッドの挙動が怪しいことになってる(という報告を貰って気づいた)ので、ちゃんとした文字列以外でもキーに使えるLookupを作らないとダメだなー、と。

GroupByでのcompareSelector

そんなわけで、GroupByのキーが全部文字列に変換されてしまう、というアレゲなバグが修正されました。あと、オーバーロードを足して、compareKey指定も出来るようにしました。何のこっちゃ?というと、例えばDateもオブジェクトも参照の比較です。

alert(new Date(2000, 1, 1) == new Date(2000, 1, 1)); // false
alert({ a: 0} == { a: 0 }); // false

JavaScriptではどちらもfalse。別のオブジェクトだから。C#だとどちらもtrue、匿名型もDateTimeも、値が比較されます。そんなわけでJavaScriptで値で比較したい場合はJSONにでもシリアライズして文字列にして比較すればいいんじゃね?とか適当なことを言ってみたりはしますが、実際Linqだと参照比較のみだと困るシーン多いんですねえ。そんなわけで、GroupBy/ToLookup、その他多数のメソッドに比較キー選択関数を追加しました。例を一つ。

var objects = [
    { Date: new Date(2000, 1, 1), Id: 1 },
    { Date: new Date(2010, 5, 5), Id: 2 },
    { Date: new Date(2000, 1, 1), Id: 3 }
]
 
// [0] date:Feb 1 2000 ids:"1" 
// [1] date:Jun 5 2010 ids:"2"
// [2] date:Feb 1 2000 ids:"3" 
var test = Enumerable.From(objects)
    .GroupBy("$.Date", "$.Id",
        function (key, group) { return { date: key, ids: group.ToString(',')} })
    .ToArray();

キーにDateを指定し、日付でグルーピングしたいと思いました(この程度の指定で関数書くのは面倒くさいし視認性もアレなので、文字列指定は非常に便利です)。しかし、それだけだと、参照比較なので同じ日付でも別物として扱われてしまうのでグルーピングされません。$.Date.toString()として文字列化すれば同一日時でまとめられるけれど、Keyが文字列になってしまう。後で取り出す際にKeyはDateのまま保っていて欲しい、といった場合にどうすればいいか、というと、ここで今回新設した第四引数のcompareSelectorの出番です。

// [0] date:Feb 1 2000 ids:"1,3"
// [1] date:Jun 5 2010 ids:"2"
var test2 = Enumerable.From(objects)
    .GroupBy("$.Date", "$.Id",
        function (key, group) { return { date: key, ids: group.ToString(',')} },
        function (key) { return key.toString() })
    .ToArray();

比較はキー(この場合$.Date)をtoStringで値化したもので行う、と指定することで、思い通りにグループ化されました。なお、C#でもこういうシーン、割とありますよね。C#の場合はIEqualityComparerを指定するのですが、わざわざ外部にクラス作るのは大変どうかと思う。といった時はAnonymousComparerを使えばlinq.jsと同じようにラムダ式でちゃちゃっと同値比較出来ます。

なお、今回からGroupByの第三引数(resultSelector)が未指定の場合はGroupingクラスが列挙されるように変更されました。GroupingはEnumerableを継承しているので全てのLinqメソッドが使えます。その他に、.Key()でキーが取り出しできるというクラスです。

Lookup

LookupはGroupByの親戚です。むしろGroupByは実はToLookupしたあと即座に列挙してるだけなのだよ、ナンダッテー。で、何かというとMultiDictionaryとかMultiMapとか言われてるような、一つのキーに複数個の要素が入った辞書です。そして、immutableです。不変です。変更出来ません。

var list = [
    { Name: "temp", Ext: "xls" },
    { Name: "temp2", Ext: "xLS" },
    { Name: "temp", Ext: "pdf" },
    { Name: "temp", Ext: "jpg" },
    { Name: "temp2", Ext: "PdF" }
];
 
var lookup = Enumerable.From(list).ToLookup("$.Ext", "$.Name", "$.toLowerCase()");
 
var xls = lookup.Get("XlS"); // toLowerCaseが適用されるため大文字小文字無視で取得可
var concat = xls.ToString("-"); // temp-temp2 <- lookupのGetの戻り値はEnumerable
var zero = lookup.Get("ZZZ").Count(); // 0 <- Getで無いKeyを指定するとEnumerable.Emptyが返る
 
// ToEnumerableでEnumerableに変換、その場合はGroupingクラスが渡る
// Groupingは普通のLinqと同じメソッド群+.Key()でキー取得
lookup.ToEnumerable().ForEach(function (g)
{
    // xls:temp-temp2, pdf:temp-temp2, jpg:temp
    alert(g.Key() + ":" + g.ToString("-"));
});

ToLookup時に第三引数を指定すると、戻り値であるLookupにもその比較関数が有効になり続けます。今回はtoLowerCaseを指定したので、大文字小文字無視でグルーピングされたし、Getによる取得も大文字小文字無視になりました。なお、GroupByでもそうですが、キーは文字列以外でも何でもOKです(compareSelectorを利用する場合はその結果が数字か文字列か日付、そうでない場合はそれそのものが数字か文字列か日付を使う方が速度的に無難です、後で詳しく述べますが)。

Dictionary

Lookupは内部でDictionaryを使うためDictionaryも作成、で、せっかく作ったのだから公開しますか、といった感じにToDictionaryが追加されました。ToObjectと違い、文字列以外をキーに指定出来るのが特徴です。

// 従来は
var cls = function (a, b)
{
    this.a = a;
    this.b = b;
}
var instanceA = new cls("a", 100);
var instanceB = new cls("b", 2000);
 
// オブジェクトを辞書がわりに使うのは文字列しか入れられなかった
var hash = {};
hash[instanceA] = "zzz";
hash[instanceB] = "huga";
alert(hash[instanceA]); // "huga" ([Object object]がキーになって上書きされる)
 
// linq.jsのDictionaryを使う場合……
// new Dictionaryはできないので、新規空辞書作成はこれで代用(という裏技)
// 第三引数を指定するとハッシュ値算出+同値比較にその関数を使う
// 第三引数が不要の場合はToDictionary()でおk
var dict = Enumerable.Empty().ToDictionary("", "",
    function (x) { return x.a + x.b });
 
dict.Add(instanceA, "zzz");
dict.Add(instanceB, "huga");
alert(dict.Get(instanceA)); // zzz
alert(dict.Get(instanceB)); // huga
 
// ...といったように、オブジェクト(文字列含め、boolでも何でも)をキーに出来る。
// ToEnumerableで列挙も可能、From(obj)と同じく.Key .Valueで取り出し
dict.ToEnumerable().ForEach(function (kvp)
{
    alert(kvp.Key.a + ":" + kvp.Value);
});

空のDictionaryを作りたい場合は、空のEnumerableをToDictionaryして生成します。微妙に裏技的でアレですが、まあ、こういう風に空から使うのはオマケみたいなものなので。というかToDictionaryメソッド自体がオマケです。DictionaryはLookupに必要だから作っただけで、当初は外部には出さないつもりでした。

第三引数を指定しないとオブジェクトを格納する場合は線形探索になるので、格納量が多くなると重くなります(toStringした結果をハッシュ値に使うので、Dateの場合は値でバラつくので大丈夫です、普通のオブジェクトの場合のみ)。第三引数を指定するとハッシュ値の算出にそれを使うため、格納量が増えても比較的軽量になります(ハッシュ衝突時はベタにチェイン法で探索してます)。なお、第三引数はハッシュ関数、ではあるのですが、それだけじゃなくて同値比較にも利用します。GetHashCodeとEqualsが混ざったようなものなので、ようするにAnonymousComparerのデフォルト実装と同じです。

勿論、ハッシュ関数と同値比較関数は別々の方が柔軟性が高いんですが(特にJavaScriptはハッシュ関数がないから重要性は高いよね!)、別々に設定って面倒くさいしぃー、結局一緒にするシーンのほうが割と多くない?と思っているためこのようなことになっています。というだけじゃなくて、もしequalsとgetHashCodeを共に渡すようにするなら{getHashCode:function(), equals:function()} といった感じのオブジェクト渡しにすると思うんですが、私はIntelliSenseの効かない、こういうオブジェクト渡しが好きではないので……。

メソッドはIDictionaryを模しているためAdd, Remove, Contains, Clear、それにインデクサが使えないのでGet, Set、EnumerableではないかわりにToEnumerableでKeyValuePairの列挙に変換。Addは重複した場合は例外ではなく上書き、Getは存在しない要素を取得しようとした場合は例外ではなくundefinedを返します。この辺はC#流ではなく、JavaScript風に、ということで。

GroupingはEnumerableを継承しているのに、DictionaryとLookupは継承していないのでToEnumerableで変換が必要です。C#準拠にするなら、Groupingと同じく継承すべきなのですが、あえて対応を分けた理由は、Groupingはそのまま列挙するのが主用途ですが、LookupやDictionaryはそのまま使うのがほとんどなので、IntelliSenseに優しくしたいと思ったのからです。90近いメソッドが並ぶと、本来使いたいGetとかが見えなくなってしまうので。

なお、Dictionaryの列挙順は、キーが挿入された順番になります。不定ではありません。JavaのLinkedHashMapみたいな感じです(C#だとOrderedDictionary、Genericsじゃないけど)。順序保持の理由は、DictionaryはLookupで使う->LookupはGroupByで使う->GroupByの取り出し順は最初にキーが見つかった順番でなければならない(MSDNにそう記載がある)。といった理由からです。ちなみにですが、GroupByが順番通りに来るってのは経験則では知ってたのですがMSDNに記載があったのは見落としていて、むしろ不定だと考えるべきじゃないか、とかTwitterでデマ吹いてたんですが即座にツッコミを頂いて大変助かりました、毎回ありがとうございます。

Share, Let, MemoizeAll

そして3つの新メソッド。これらはReactive Extensionsに含まれるSystem.Interactive.dllのEnumerableに対する拡張メソッドから移植しています。

// Shareはenumeratorを共有する
// 一つの列挙終了後に再度呼び出すと、以前中断されたところから列挙される
var share = Enumerable.Range(1, 10).Share();
var array = share.Take(4).ToArray(); // [1,2,3,4]
var arrayRest = share.ToArray(); // [5,6,7,8,9,10]
 
// 例えば、これだと二度列挙してしまうことになる!
// 1,1,2,3とアラートが出る
var range = Enumerable.Range(1, 3).Do("alert($)")
var car = range.First(); // 1
var cdr = range.Skip(1).ToArray(); // [2,3]
 
// Shareを使えば無駄がなくなる(アラートは1,2,3)
var share = range.Share();
var car = share.First(); // 1
var cdr = share.ToArray(); // [2,3]

Shareは、列挙の「再開」が出来ると捉えると良いかもしれません。ちなみに、再開が出来るというのは列挙完了までDispose(終了処理)しないということに等しいのには、少しだけ注意が必要かもしれません。

// Letの関数の引数は自分自身
// [1,2], [2,3], [3,4], [4,5]
Enumerable.Range(1, 5).Let(function (e)
{
    return e.Zip(e.Skip(1), "x,y=>[x,y]");
});
 
// 上のLetはこれと等しい
var range = Enumerable.Range(1, 3);
range.Zip(range.Skip(1), "x,y=>[x,y]");
 
// 余談:Pairwiseは↑と同じ結果です、一つ先の自分との結合
Enumerable.Range(1, 5).Pairwise("x,y=>[x,y]");

Letは、Enumerableを受け取ってEnumerableを返す関数を渡します。何のこっちゃですが、外部変数に置かなくても、メソッドチェーンを切らさずに自分自身が使えるということになります。使い道は主に自分自身との結合を取りたい場合、とか。なお、何も対処せずそのまま結合すると二度列挙が回ることには注意が必要かもしれません。

// MemoizeAllは、そこを一度通過したものはキャッシュされる
var memo = Enumerable.Range(1, 3)
    .Do("alert($)")
    .MemoizeAll();
 
memo.ToArray(); // 一度目の列挙なのでDoでalertが出る
memo.ToArray(); // 二度目の列挙はキャッシュからなのでDoを通過しない
 
// Letと組み合わせて、自己結合の列挙を一度のみにする
Enumerable.Range(1, 5)
    .MemoizeAll()
    .Let(function (e) { return e.Zip(e.Skip(1), "x,y=>[x,y]") });

MemoizeAllはメモ化です。二度三度列挙するものは、一度.ToArray()とかして配列に置いたりすることが少なくなかったのですが、MemoizeAllはその辺を遅延評価のままやってくれます。使いかっては良さそう。ただし、これもShareと同じく列挙完了まで例外が起ころうが何だろうがDispose(終了処理)しないのは注意が必要かもしれません。素のJavaScriptではリソース管理は滅多にないので関係ないですが、例えばメモ化が威力を発揮しそうな(linq.jsではWSHで、C#だったら普通にやりますよね)ファイル読み込みに使おうとすると、ちょっと怖い。ていうか私はそういう場合は素直にToArrayします。

この3つは「注意が必要かもしれません」ばかりですね!ただ、MemoizeAll->Let->Zipで自分自身と結合するのは便利度鉄板かと思われます。便利すぎる。

まとめ

Dictionaryの導入は地味に影響範囲が大きいです。集合演算系メソッドもみんなDictionary利用に変えたので。なんでかというと、ええと、そう、今まではバグっておりました……。trueと”true”区別しないとか。Enumerable.From([”true”,true]).Distinct()の結果が[”true”]になってました。ほんとすみません。今回から、そういう怪しい挙動は潰れたと思います。いや、Dictionaryがバグッてたら元も子もないのですが多分大丈夫だと思います思いたい。

とにかく一年前の私はアホだな、と。来年も同じこと思ってそうですが。

Share, MemoizeAll, LetのRx移植三点セットは遊んでる分には面白いんですが、使いどころは難しいですね。ちなみに、MemoizeAllということで、RxにはAllじゃないMemoizeもあるのですが、かなり挙動が胡散臭いので私としては採用見送りだし、C#でも使う気はしません。内部の動きを相当意識してコントロールしないと暴走するってのが嫌。

VS2010のJavaScript用IntelliSenseは本当に強化されましたねー。ということで、VS2010ならDictionaryやLookupでIntelliSenseが動作するのですが、VS2008では動作しません。完全にVS2010用に調整してたら2008では動かないシーンも幾つか出てきてしまった……。まあでも、2010は素敵なのでみんな2010使えばいいと思うよ。私はまだExpressですががが。一般パッケージ販売まだー?(6月です)

そういえば、2.0は公開からひと月でDL数100到達。本当にありがとうございます。そろそろ実例サンプルも作っていきたいのですがネタがな。Google Waveって結局どうなんでしょうかね。ソーシャルアプリは何か肌に合わない雰囲気なのでスルーで、Waveに乗り込むぜ、とか思ってたんですが思ってただけで乗り込む以前に触ってもいないという有様。HTML5は、ええと、基本はC#っ子なのでSilverlight押しだから。うー、じゃあChrome拡張辺りで……。いやでもHTML5かな、よくわからないけれど。

今のところ最優先事項はlinq.javaなんですけどね、忘れてない忘れてない。というか、linq.js 2.0も今回のOrderByのロジック変更も、linq.javaで書いてあったのを持ってきただけだったりして。もうほんと、とっとと出したいです。出してスッキリして次に行きたい。

そんなわけで、この2.1でlinq.jsはようやくスタート地点に立てたぜ、という感じなので使ってやってください。

Prev |

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for .NET(C#)

April 2011
|
March 2017

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc