Archive - JavaScript
linq.js ver 2.1.0.0 - ToDictionary, Share, Let, MemoizeAll
- JavaScript linq.js - 10.05/18
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はようやくスタート地点に立てたぜ、という感じなので使ってやってください。
linq.js ver 2.0 / jquery.linq.js - Linq for jQuery
- JavaScript linq.js - 10.04/23
無駄に1280×720なので、文字が小さくて見えない場合はフルスクリーンにするかYouTubeに飛んでそちらで大きめで見てください。というわけで、動画です。linq.js + Visual Studio 2010で補完でウハウハでjQueryプラグインで世界系です。ここ最近、RxJS、JSINQとJavaScript系の話が続く中で、ふと、乗るしかない このビックウェーブに、という妄念が勢いづいてlinq.jsをver.2.0に更新しました。
内部コードを全面的に変更し、丸っきり別物になりました。破壊的な変更も沢山あります。名前空間がE、もしくはLinq.EnumerableだったのがEnumerableになり、幾つかのメソッドを廃止、廃止した以上にメソッドを大量追加。そして、WindowsScriptHostに対応しました。その他色々細かい変更事項の詳細は下の方で。あ、そうそう、名前空間はEnumerableを占有するので、prototype.jsとは被るため一緒に使えません。
今回の最大のポイントは、jquery.linq.jsという、jQueryのプラグイン化したlinq.jsを追加です。基本機能は完全に同一ですが、jQueryプラグイン版のみの特徴として、jQueryとEnumerableとの相互変換用のメソッドが定義されています。呼び出しは$.Enumerableを使います(グローバル名前空間は一つも汚しません)。
Linqとは?
耳タコなぐらい繰り返していますが、C#知らない人やこのサイトを始めて訪れた人にも使って欲しいんです!ということで、Linqとは何かを紹介します。簡単に言うと、そのふざけたforをぶち殺す。ための代物。Linq導入以降、当社比100%でfor, foreachの出番はなくなりました。ifも半減。ネスト量激減。えー、forー?forなんて使うの小学生までだよねー。
Linq(to Objects)とは?便利コレクション処理ライブラリです。語弊は、大いにある。あるのだけど、言い切るぐらいでいいと思うことにしている近頃。具体的には、mapやfilterやreduceが使えます。非破壊的なsortができます。Shuffle(ランダムに並びかえ)やDistinct(重複除去)などがあります。流れるようにメソッドチェーンで記述出来ます。index付きのforeachが書けます。DOMに対しても同じようにforeachやmapを適用できます。遅延評価が基本になっているので、幾つmapやfilterを繋げても、ムダの無い列挙が可能になっています。また、無限リストが扱えます。JavaScriptでプログラミングするにあたって不足しまくるコレクション処理を大幅に下支えするのが、linq.jsです。
Linqの世界、jQueryの世界
100億の言葉より1の実例。
// こんな感じ(1から10個、偶数のみを二乗したのをアラートに出す) $.Enumerable.Range(1, 10) .Where("$%2==0") // 他言語でfilterとか言われているもの .Select("$*$") // 他言語でmapとか言われているもの .ForEach("alert($)"); // Linq側はTojQueryでjQueryオブジェクトに変換 $.Enumerable.Range(1, 10) .Select(function (i) { return $("<option>").text(i) }) .TojQuery() .appendTo("#select1"); // jQueryオブジェクト側はtoEnumerableでLinqに変換 var sum = $("#select1").children() .toEnumerable() .Select("parseInt($.text())") .Sum(); // 55
シームレスに結合されているようであまり統合されてはいません。お互い、コレクション操作中心であったり、連鎖で操作するという形なので、融け合うことはできません。toEnumerableに関しても、jQuery世界とは切り離された、Linqの世界へと移行するだけ。とはいえ、toEnumerableとTojQueryにより、チェーンを切らさずに相互変換して、スムーズに世界を切り替えられるというのは、中々に楽しい。
図にするとこんな感じ。jQueryの世界はメソッド名小文字、Linqの世界はメソッド名大文字、というので自分の今居る世界が分かります。Linqは世界系。世界に囚われると逃げ出すことは出来ないのです!いくらもがいても(Select)もがいても(Where)変わらない。鏡の中に逃げこむか(tojQuery)、鏡を割ってしまうか(ToArray)、どちらにせよ、代償を払うわけですね? 意味不明。そんな感じで非常に楽しいです。
IDE Lover
linq.jsの特徴として、入力補完サポートを徹底的に行っているという点があります。なぜなら、私自身がIDE大好きっ子、入力補完大好きっ子だから。テキストエディタで書いているとか言う人は、そのエディタは窓から投げ捨ててIDE使おうぜ! どんな感じで書けるかというと、動画を見てください、一番上の。今回は無料のVisual Web Developer 2010 Expressを使っています。インストールも拍子抜けするぐらい簡単ですよ!超お薦め。HTML/JSエディタとして使うポイントは、「Webサイトを作成」から「空のASP.NETウェブサイト」を選ぶことです。詳しくはまた後日にでも。
linq.js ver.2.0.0.0
では、本体の更新事項の方もご紹介。jquery.linq.jsも、この2.0から作られているので以下の事項は当てはまります。まず、名前空間を大々的に弄りました。Linq名前空間は全面的に廃止、Enumerableのみに。また、ショートカットとして用意していたEも廃止。それとLinq.ObjectとLinq.Enumerableは統合されて、Enumerableになりました。
次に廃止ですが、いっぱいあります。ToJSONの廃止、ちゃんと出力可能なのかを保証出来ないのでやめた。JSONは専用ライブラリ使ってください。ToTableの廃止、明らかに不要で邪魔臭かったから。なんでこんなもの搭載したのかすら謎。TraceFの廃止、というか、今までのTraceFをTraceとし、TraceFを消滅させました。TraceFはconsole.logに書き込んでいたのですが、IE8からはIEでも使えるようなので、F(Firebug)じゃなくていいかな、と。あと元のTraceはdocument.writeだったので、それは意味ねーだろ、ということで。あとは、RangeDownToの廃止。かわりにRangeToをマイナス方向への移動に対応させました。これは、ToなんだからUpとDownを区別するほうがオカシイってことにやっと気づいたからです、しょぼーん。
次に名前変更の類。ZipWithの名称をZipに変更(.NET 4.0と名前を合わせるため)。Sliceの名称をBufferWithCountに変更(Rxと名前を合わせるため)。Makeの名称をReturnに変更(Rxと名前を合わせるため)。Timesの名称をGenerateに変更(Rxと名前を合わせるため、RxのGenerateは基本Unfoldですが)。
新メソッドとしてMaxBy, MinBy, Alternate, TakeExceptLast, PartitionBy, Catch, Finallyを追加しました。MaxByとMinByはそのまんま。Alternateは一個毎に値を挟み込みます。TakeExceptLastは最後のn個(引数を省いた場合は1個)を除いて列挙します(Skipの逆バージョンみたいなもの)。PartitionByはGroupByの特殊系みたいな。CatchとFinallyはエラーハンドリング用。いつもなら、ここで例を出すところなのですが今回は省略、詳しくはリファレンスのほうで。
それと、OfTypeの追加。JavaScriptは型がゆるふわだからCastもOfTypeもイラナイし!と思ってたんですが、そしてCastは実際いらないのですが、OfTypeは翌々考えてみると型がゆるふわだからこそ、フィルタリングするために超絶必要じゃないか!ということに気づきました。ので追加。そして、これでCast以外は.NETのLinq演算子の全てを搭載ということになりました。
Windows Script Host
最後に、Enumerable.FromをJScriptのEnumeratorに対応させました(linq.jsのEnumeratorじゃなくて組み込みの方ね)。これにより、Windows Script Hostやエディタマクロでlinqが使えるようになりました。Linq for WSH!えー、WSH?WSHなんて使うの小学生までだよねー。今はPowerShellっすよ。と思うかもしれませんが違います。WSHならばJavaScriptで強烈に補完効かせながらガリガリ書けるのです。しかもLinqが使える。むしろPowerShellの時代は終わったね、WSH復権の時よ来たれ!だからそろそろアップデートかけて.NET Frameworkのライブラリが全面的に使えるようになって欲すぃ(今は、極一部のは使えないことはないのですが相当微妙)。では実例など少し。
// フォルダ下のフォルダ名とファイル名を取得する var dir = WScript.CreateObject("Scripting.FileSystemObject").GetFolder("C:\\"); // 通常の場合 var itemNames = []; for (var e = new Enumerator(dir.SubFolders); !e.atEnd(); e.moveNext()) { itemNames.push(e.item().Name); } for (var e = new Enumerator(dir.Files); !e.atEnd(); e.moveNext()) { itemNames.push(e.item().Name); } // linq.js var itemNames2 = Enumerable.From(dir.SubFolders) .Concat(dir.Files) .Select("$.Name") .ToArray();
WSHでJScriptを使う場合の最大の欠点は、foreachがないこと、でしょうか。生のEnumeratorを回すのは苦痛でしかない。しかし、linq.jsを用いればFrom.ForEachで簡単に列挙できます。それだけでなく、Linqの多様なメソッドを用いて自然なコレクション操作が可能です。上記例では、Concatでファイル一覧とサブフォルダ一覧を連結することで、名前の取り出しを共通化することができました。
WSHに関して詳しくはウェブで、じゃなくて後日、と言いたいのですが(既に記事がかなり長いので)、そんなこと言ってるといつまでたっても書かなさそうなので、簡単に説明を。WSH(WindowsScriptHost)とはWindows用のスクリプト環境で、自動化など、非常に強力で大抵のことが出来ます。で、標準ではVBScriptと、JScriptで書けます。つまりJavaScriptで書ける。つまりlinq.jsが読み込める。つまりLinqがWSHでも使える。WSHで扱うあらゆるコレクションに対して、Linqを適用させることが可能です。WSHだけでなく、Windows上でJScriptを用いるもの、例えばWindowsのデスクトップガジェットなどでも利用することが出来ます。
ライブラリを用いる場合は、wsfファイルで記述すると良いかもです。中身は簡単なXMLで、下のような感じに。
<job id="Main"> <script language="JScript" src="linq.js"></script> <script language="JScript"> function EnumerateLines(filePath) { return Enumerable.RepeatWithFinalize( function () { return WScript.CreateObject("Scripting.FileSystemObject").OpenTextFile(filePath) }, function (ts) { ts.Close() }) .TakeWhile(function (ts) { return !ts.AtEndOfStream }) .Select(function (ts) { return ts.ReadLine() }); } EnumerateLines("C:\\test.txt").Take(10).ForEach(function (s) { WScript.Echo(s); }); </script> </job>
そうそう、自動Closeも含めたリソース管理も出来ます。ストリームなどにはRepeatWithFinalizeを使ってLinq化することで、Closeまで一本にまとめあげられます。といったような複雑なことが必要なくても、E.From(collection).ForEach()がふつーに便利です。というか、こんな単純なことすら素の状態じゃ出来ないJScriptに絶望した!でもそれがいい。JScript可愛いよJScript。
エディタマクロ
WSHによるJavaScript実行はEmEditorやサクラエディタ、Meryなど様々なテキストエディタで利用できます。せっかくなのでちょっと実用的なものを、と思って電卓を作ってみました。EmEditor用マクロです。選択範囲内の一行を計算し、その行の右側に出力します。ちゃちゃっと計算出来て便利。計算し直しを頻繁にする時は、新規ファイルを開いてまっさらなものに数式を書いて、Ctrl+A -> マクロ実行を繰り返すと良い感じ。
#include = "linq.js" with (document.Selection) { Enumerable .RangeTo(GetTopPointY(eePosLogical), GetBottomPointY(eePosLogical)) .Select(function($i) { var $line = document.GetLine($i); try { eval($line) } catch ($e) { } // SetVariableToGlobal var $expr = $line.split("=")[0].replace(/^ +| +$/g, ""); try { var $result = eval($expr) } catch ($e) { } return { PointY: $i, Result: $result, Line: $expr } }) .Where(function(a) { return a.Result !== undefined }) .ForEach(function(a) { SetActivePoint(eePosLogical, 1, a.PointY, false) SelectLine(); Delete(); document.writeln(a.Line + " = " + a.Result); SelectLine(); UnIndent(); StartOfLine(); }); }
一行のうち=の左側をevalして、戻り値を返すものは出力、evalに失敗したものはスルー。ということなので、JavaScriptのMath関数は全部使えます。Select内の変数が$iとか、$始まりなのは変数定義による名前の衝突を避けるためです。慣れないマクロ書きなので色々酷い箇所も多いとは思いますが、なんとなく伝わるでしょうか? テキストエディタのマクロは、一行ずつの配列として、一行に対して操作をかけることが多いので、RangeToで範囲を作って、行ごとに適当に変形(Select)させて例外条件を省いて(Where)、処理(ForEach)。煩雑になりがちな行番号管理などを、流れるように簡単に記述出来ます。
なお、EmEditorは先頭に#includeを記述するだけでライブラリ読み込みが出来ますが、インクルード機能のないエディタでは以下のようなコードを先頭に置くことでライブラリが読み込めます。
eval(new ActiveXObject("Scripting.FileSystemObject").OpenTextFile("linq.js").ReadAll().replace(/^・ソ/,""));
ようするに、丸ごとテキストとして読み込んでevalです。/^・ソ/はUTF-8のBOM対策(適当すぎる)。一応、Meryでlinq.jsが動くのは確認しました。電卓マクロは動きません(eePosLogicalとかがEmEditorにしかないので、まあ、その辺は当然だししょうがないわなあ)
まとめ
今回のリニューアルは大変気合が入ってます。コード全部作り替えたぐらいには。最初はjQueryプラグインだけ足せばいいや、とか思ってたのですが、破壊的変更をかけるなら中途半端はいくない、と思い、やれるのは今だけということで徹底的に作り替えました。
ちなみに、ちっとも気にしてないパフォーマンスは悪化しました。ver.1.xがそれでもまだシンプルな構造だったのに比べ、今回は一回の呼び出し階層がかなり深くなった影響があります。列挙終了後にRepeatWithFinalizeとFinallyでしか使ってないDisposeのために階層駆け上がるし(これほんと入れようか悩んだんですけど、WSHだけでなく、将来的にはJavaScriptでもきっちりリソース管理でCloseって機会も増えそうなので入れました)。しかし遅いといっても、ベンチ取るとChromeなら爆速で誤差範囲に収まってしまうのですよ!Google Chrome、恐ろしい子。Firefoxだと?聞くな。いえいえ、JSINQよりは速かったですよ?←何だこの対抗心は
jQueryのおともに。WSHのおともに。エディタマクロのおともに。調度良い感じの隙間にピタリはまるようになっております。JavaScriptにおいてコレクションライブラリはニッチ需要だし、WSH用ライブラリなんて完全隙間産業なわけですが、むしろだからこそ、幾分か価値があるかな?合計83メソッドと、大充実なので、きっと要求に答えられると思います。
linq.jsをjQueryと一緒に使う
- JavaScript linq.js - 10.04/15
linq.jsとjQueryを一緒に使うとするとどうなるのかな、という今まで微妙に避けてきた話。実用的なことを考えるなら避けて通れないのですが、実用的なことなんて考えたこともなかった!今まで!すみません。というわけで、少し考えてみます。あ、Linq to Xml(linq.xml.js)はボツの方向で行きます。RxJSを見習って、jQueryと仲良くやる方向で考えるつもりです。割とイイ線行ってたかなあ、とは思うんですが、クロスブラウザ対応の面倒くささが半端なくて実装しきる気力が持ちそうにないのと、やっぱセレクタ操作が冗長になりすぎてダメだったかなあ、なんて思ってます。
こんな風なことができるのかと思ったがそんなわけはなく。var p_arr = from p_ele in $("p") where "hoge" in p_ele.classes select p_ele for(var p in p_arr){ p.toggleClass("hilight"); }
Linq.js - 人生がベータ版
お試しありがとうございます。この例は、出来るといえば出来るし、出来ないといえば出来ないです。まず、クエリ構文では書けなくて、というのはともかくメソッド構文で実際にlinq.jsで組んでみるとこうなります。
E.From($("p")) // 内包するDOM要素を列挙 .Select("jQuery($)") // 単一要素のjQueryオブジェクトにラップ .Where(function (q) { return q.attr("class") == "hoge" }) .ForEach(function (q) { q.toggleClass("hilight") }); // jQueryで同じような形にするならこうでしょうか $("p.hoge").each(function () { var q = $(this); q.toggleClass("hilight"); });
FromでjQueryの選択された要素を列挙に変換します。jQueryの列挙はjQueryオブジェクトではなく、DOM要素をそのまま返すので(これは.eachで回した時も一緒ですね)、jQueryのメソッドを使いたい時は再度ラップしてあげます。ただまあ、単純なフィルタリングなら、jQueryのセレクタで書いたほうが当然すっきり仕上がりますね。更にまあ、そもそもeachする必要はなく、
// jQuery自体が集合を扱うので一行で書けるんですよね…… $("p.hoge").toggleClass("hilight");
となってしまうわけで、DOMから選択してDOMに作用を加える、という一連のjQueryチェーンで綺麗に成り立っている場合に、linq.jsを挟む余地はありません。jQuery自体が集合を含有しているので、jQueryだけで綺麗に完結しちゃうんですね。そしてlinq.jsは、それ自体はクロスブラウザ対応だったりのDOM操作は一切持っていないので、その辺はjQueryにお任せします。そうなると、どうしても出番が限られてくるという。
じゃあ無価値なのかといえば勿論そんなことはなくて、DOMをDOMのまま扱って抽出して作用を加えるのではなく、そこからテキストだったり数値を抽出する、というシーンでは生き生きとします。DOMの選択、フィルタリングまではjQueryのセレクタで行ったらlinq.jsに渡す。数値だったら集計系のメソッドが使えるし、他にもテーブル内の文字をキーにして複数テーブルの結合(join)、なんかも簡単に出来ます。
ところで、 E.From($(”selector”)).Select(”jQuery($)”) というのは定型文になるので、jQuery自体に拡張してしまいます。こんなんでもプラグインって呼んでいいですか?(プラグインとしての部分は一行でも、繋がってる先はある意味ヘビー級なので)
// これでjQueryオブジェクトからtoEnumerable()を呼ぶとlinqが使える jQuery.fn.toEnumerable = function () { return E.From(this).Select("jQuery($)"); } // 例えば、input要素の数値を合計する、とか var sum = $("input").toEnumerable() .Select("parseInt($.val())") .Sum();
割と便利。かな?具体例に乏しくて非常に説得力に欠ける感じですが、良ければ使ってやってください。
Linq to ObjectsをJavaScriptに実装する方法
- JavaScript linq.js - 10.04/11
JavaScriptでLINQを使おう - 複雑な検索処理を簡潔に記述する「JSINQ」という記事が出ました。私はlinq.jsという、同種のJavaScriptへのLINQ移植ライブラリを作成している人間のため、JSINQの人気っぷりに思わず嫉妬してしまった(笑)のですが、そういう感情は抜いておいてこの紹介記事は、今ひとつよろしくない。
まず頂けないのが、列挙の方法。
// 生のenumeratorを取り出して列挙するですって!? while (enumerator.moveNext()) { var name = enumerator.current(); document.write(name + '<br>'); } // eachが用意されているというのに! result.each(function(name) { document.write(name + "<br />") });
C#でもJavaでも、きっと他の言語でも、反復子をwhileループで回すなんて原始的なことは普通やりませんよね? foreachに渡しますよね? そんなわけでJSINQにはeachメソッドが用意されているのですが紹介記事は普通にスルー。「enumeratorでの列挙とeachでの列挙二つ紹介する」「enumeratorのみ紹介する」「eachのみ紹介する」の三択で、スペースの都合上一つしか紹介出来ないなら、eachのほうを紹介すべきでしょう。いやまあ、例が一個だったらしょうがないなあ、どうせJSINQのチュートリアルの上のほうから抜き取っただけだろうしー、と思うのですが、三個もenumerator取り出しの例を出されるとさすがにオイオイオイオイ、と突っ込みたくなる。
もうひとつは、文字列によるクエリ構文を推しすぎ。JSINQの最大の特徴でもある部分なのでJSINQの紹介としては正しいのですが(JSINQのプロジェクトページでもそれをフィーチャーしてますしね)、LINQの紹介として見ると大変頂けない。.NETを知らない人(JavaScriptのライブラリなので、基本はJavaScriptの人が見るでしょう)がLinqを誤解してしまう要因になりうるので、こういった紹介は割とキツい。
LINQとはLanguage Integrated Query(統合言語クエリ)であり、言語に統合されていてこそLinqなのです。文字列で与えたらSQLと一緒。LinqはしばしばSQLっぽく記述するもの、と誤認されているようですが、違います。文字列で与えていたSQL(こんな風にね、と最近作ったDbExecutorというSQL実行簡易補助ライブラリをどさくさに紛れて紹介してみる)とは全く別物なのです。詳細は説明すると長くなるので省いちゃいます(え?)。理屈はともかく、言語に統合されていない状態でのSQLは書きやすいとはいえないわけですよ?
var elements = document.getElementsByTagName('a'); var enumerable = new jsinq.Enumerable(elements); var query = new jsinq.Query(' \ from e in $0 \ where e.href.indexOf("google.co.jp") > -1 \ select e \ '); query.setValue(0, enumerable); var result = query.execute(); var enumerator = result.getEnumerator(); while (enumerator.moveNext()) { var e = enumerator.current(); document.write(e.text + ': ' + e.href + '<br>'); }
改行のために末尾に\を入れなければならない、不恰好なプレースホルダ、クエリコンパイルの必要性(executeメソッドの実行でメソッドチェーン形式に変換されます、面白いことにこの点まで.NET Frameworkの忠実な再現となっています(クエリ構文はメソッド構文の糖衣構文にすぎない))。というわけで、到底書きやすいとは言えません。この例を見て、長げーよ馬鹿、意味ねー、アホじゃねーの?普通にfor回した方が百億倍マシだろ、と思った人もいるでしょう。その通りです。素直に便利かも……とか思ったなら、物事はもう少し冷静に見るようにしてください。しかしメソッド構文(jQueryのようにメソッドチェーンで書く方法)ならこう書けます。
var elements = document.getElementsByTagName('a'); new jsinq.Enumerable(elements) .where(function(e) { return e.href.indexOf("google.co.jp") > -1 }) .each(function(e) { document.write(e.text + ": " + e.href + "<br>") });
これなら納得で、割と使えるかもって感じではないでしょうか? JSINQにおける文字列によるクエリ構文は、人を釣るためのただの餌です。そんな餌で俺様が釣られクマー。jSINQをJavaScriptライブラリとして使うのならば、メソッド構文のほうをお薦めします。クエリ構文はネタ、もしくはただの技術誇示にすぎません。よくやるなー、って感じで素晴らしいとは思いますが、実用性は皆無です。JSINQ自体はLinqの移植として割と良く出来ているので(何だこの上から目線)、文字列クエリ構文で試してみて使えないなー、と思ってしまった、もしくは紹介を見て文字列クエリ構文とかこのライブラリダメだろ、と思った人は、その辺は誤解なくどうぞ。
linq.js
LinqのJavaScript実装は他にもあります。一つは、ええと、私の作成しているlinq.jsです。売り文句はJSINQと同じくSystem.Enumerableとの完全なるAPI互換。.NET4までの範囲を全てカバーしています。更にその上に、Achiral, Ruby, Haskellなどから参考にした大量のメソッドが追加されていることと、Visual Studioで使う場合にはIntelliSenseが動作するファイルがあること、などなど「実用的に使う」ことを強く意識して作っています。手前味噌なのでアレですが、他のどのライブラリよりも使える度は高いと思っています。
- linq.js - LINQ for JavaScript Library - プロジェクトページ
- 紹介と簡単なチュートリアル
- 入力補完に対応させたのでVSでの利用法紹介
- ブログ記事のlinq.jsカテゴリ(最後の更新が去年の9月、がーん)
更新が微妙に止まっているのですが、WindowsScriptHostで快適に使えるような追加ライブラリを作成中(と、9月に言ったっきり絶賛作業休止中、すみません、でもやる気はあるので遠くないうちに必ず出します)。あと、Reactive Extensions for JavaScriptという、これまた.NET発のJavaScript移植ライブラリが出ているので、それとの協調動作も考えています。
Linqを自分で実装する
では本題。実際にLinqをJavaScriptで実装してみましょう。C#でSelectを実装してみたことはありますか? 何のことはなく、たった1行で出来ちゃうんですよね。そんなわけで、実際のところ別に難しいことはありません。勿論、全てのAPIを網羅するのは面倒くさいですが、基本的な原理を掴んでおくとグッと利用法が広がるはずです。まずは、一番単純な、Array.prototypeに生やす方法を考えてみます。例としてmapとforEachを実装してみましょう。
Array.prototype.map = function(selector) { var result = []; for (var i = 0; i < this.length; i++) result.push(selector(this[i])); return result; } Array.prototype.forEach = function(action) { for (var i = 0; i < this.length; i++) action(this[i], i); // with index } var array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; array.map(function(i) { return { Single: i, Double: i * 2} }) .forEach(function(a) { alert(a.Single + ":" + a.Double) });
配列を変形してforeach。非常に単純な代物ですが、単純が故にmapやfilterは便利ですよね、かなり多用します。C#における匿名型は、JavaScriptではそのままハッシュを返すことで実現されます。さて、このやり方には問題が二つあります。一つはビルトインオブジェクトのprototypeを拡張する、微妙なお行儀の悪さ。そこで、arrayを独自オブジェクトにくるんでやりましょう。
function Enumerable(array) { this.source = array; } Enumerable.prototype.map = function(selector) { var result = []; for (var i = 0; i < this.source.length; i++) result.push(selector(this.source[i])); return new Enumerable(result); } Enumerable.prototype.filter = function(predicate) { var result = []; for (var i = 0; i < this.source.length; i++) if (predicate(this.source[i])) result.push(this.source[i]); return new Enumerable(result); } Enumerable.prototype.reduce = function(func) { var result = this.source[0]; for (var i = 1; i < this.source.length; i++) result = func(result, this.source[i]); return result; } var array = [1, 2, 3, 4, 5, 6]; var sum = new Enumerable(array) .filter(function(i) { return i % 2 == 0 }) .map(function(i) { return i * i }) .reduce(function(x, y) { return x + y }); alert(sum); // 56
配列を一旦包まなくてはならないのが煩わしいのですが、メソッドチェーンのコンボを決めて、気持ちよく列挙することが出来ます。この例ではFirefoxのfilter, map, reduceを再定義してみました(thisObjectの辺りはスルーしてますしreduceの引数なんかも違いますが)。1から6の配列のうち偶数のみを二乗して足し合わせる。答えは56。さて、しかしこの方式にも問題があります。Arrayのprototype拡張が抱えているもう一つの問題と同じですが、メソッドの一つ一つを通る度に無駄な中間配列を生成してしまっています。メソッドチェーンの形になっていると隠蔽されてしまうのですが、冷静に眺めてみればこういうことです。
var array = [1, 2, 3, 4, 5, 6]; var _array = []; for (var i = 0; i < array.length; i++) { if (array[i] % 2 == 0) _array.push(array[i]); } var __array = []; for (var i = 0; i < _array.length; i++) { __array.push(_array[i] * _array[i]); } var sum = __array[0]; for (var i = 1; i < __array.length; i++) { sum += __array[i]; }
さすがに、これはあまりのアホさと無駄さに死ね!と言いたくなりませんか?まあ、この程度は大したコストではないのも確かですし、これこそが富豪的プログラミングだ!といえば、そうだし、その辺はそんなに否定しません。些細なパフォーマンスチューニングにはあまり興味ありません。が、しかし、根本的な問題として、これだと無限リストが扱えません。無限リストとは無限に続くもの、例えば [0,1,2,…,9999,10000,…] 。そんなの使わないって?いやいや、使いこなすと存外便利ですよ? そんなわけで、富豪とか云々を抜きにしても、ただのArrayラッパーは却下です。即時評価なfilterやmapなんて使いたくありません。.NET FrameworkのLinq to Objectsは遅延評価なので、無限リストも扱えますし中間配列といった無駄は出てきません。では遅延評価のリスト処理をどう実装しましょうか。無限リストを作る方法は色々あるでしょうが、ここはLinqの移植なのでC#でのやり方と同じくイテレータパターンを用います。
IEnumerable = function(moveNext) { this.getEnumerator = function() { return { current: null, moveNext: moveNext } } } // Generator Enumerable = { toInfinity: function(from) { if (from === undefined) from = 0; return new IEnumerable(function() { this.current = from++; return true; }); } } // select as map IEnumerable.prototype.select = function(selector) { var source = this; var enumerator = null; return new IEnumerable(function() { if (enumerator == null) enumerator = source.getEnumerator(); if (enumerator.moveNext()) { this.current = selector(enumerator.current); return true; } return false; }); } // 無限に2倍するリスト[0, 1, 4, 9, 16,... Enumerable.toInfinity().select(function(i) { return i * 2 });
LinqはIEnumerableオブジェクトの連鎖で成り立っています。また、return thisでメソッドチェーンをするわけではありません。selectを見てください。メソッドが呼ばれた時点では何も実行せずに、クロージャにより環境を保持した新しいIEnumerableを生成し、それを返しています。ではいつ実行されるのかというと、getEnumerator()が呼ばれ、それで取得されたenumeratorオブジェクトのmoveNext()を呼んだ時です。
さて、しかしこのままではgetEnumeratr()で反復子を取得しての列挙しか出来なくて不便なので、forEachなどを定義してやる必要があります。また、無限リストが本当に無限のままでは困るので、停止させるものが必要です。というわけで、代表的なものを幾つか紹介します。
IEnumerable = function(moveNext) { this.getEnumerator = function() { return { current: null, moveNext: moveNext } } } // Generator Enumerable = { from: function(array) { return Enumerable.repeat(array) .take(array.length) .select(function(ar, i) { return ar[i] }); }, toInfinity: function(from) { if (from === undefined) from = 0; return new IEnumerable(function() { this.current = from++; return true; }); }, repeat: function(element) { return new IEnumerable(function() { this.current = element; return true; }); } } // select as map IEnumerable.prototype.select = function(selector) { var source = this; var enumerator = null; var index = -1; return new IEnumerable(function() { if (enumerator == null) enumerator = source.getEnumerator(); if (enumerator.moveNext()) { this.current = selector(enumerator.current, ++index); return true; } return false; }); } // where as filter IEnumerable.prototype.where = function(predicate) { var source = this; var enumerator = null; var index = -1; return new IEnumerable(function() { if (enumerator == null) enumerator = source.getEnumerator(); while (enumerator.moveNext()) { if (predicate(enumerator.current, ++index)) { this.current = enumerator.current; return true; } } return false; }); } IEnumerable.prototype.take = function(count) { var source = this; var enumerator = null; var index = -1; return new IEnumerable(function() { if (enumerator == null) enumerator = source.getEnumerator(); while (++index < count && enumerator.moveNext()) { this.current = enumerator.current; return true; } return false; }); } IEnumerable.prototype.toArray = function() { var result = []; var enumerator = this.getEnumerator(); while (enumerator.moveNext()) { result.push(enumerator.current); } return result; } // 利用例 // こんな配列があったとして var array = [1232, 421, 1, 2, 3412, 42, 4, 2, 45]; // 偶数のもののみ二倍した新しい配列を生成 var array2 = Enumerable.from(array) .where(function(i) { return i % 2 == 0 }) .select(function(i) { return i * 2 }) .toArray(); // 1-100の配列を作成 var array3 = Enumerable.toInfinity(1).take(100).toArray(); // ""のみの長さ100の配列を作成 var array4 = Enumerable.repeat("").take(100).toArray();
生成用メソッドとして、配列を反復子に変換するfrom, 無限にインクリメントした整数を返すtoInfinity, 無限に同一要素を繰り返すrepeatを定義しました。メソッドチェーン用として関数を要素に適用させるselect, 関数でフィルタリングするwhere, 指定個数取得するtake。そしてメソッドチェーンを打ちきって通常使えるオブジェクトに変換するものとして、配列に変換するtoArrayを定義。
fromがrepeatとtakeとselectの組み合わせで出来ているというのが、面白いところです。所謂Fill(配列の初期化)も、repeat->take->toArrayで出来てしまいます。小さなパーツを組み合わせてあらゆることを出来るようにするのがLinqの魅力です。
速度?これが速いと思いますか?そうですねえ、見るからに、xxxですね。しかし、私はミリセカンド単位でのパフォーマンスチューニングにはあまり興味がありません。はいはい、富豪的富豪的。実際のとこGoogle Chrome使えばIE6の1000倍速くなるんだぜ!(数値は適当)。って感じなので、JavaScript側での最適化は、あまり……。とくにLinqではDOM操作とか重たいことをやるんではなくて、純粋に、連鎖の分だけ関数呼び出しが増えるって程度でしかないので、この程度のことでムダムダムダムダー、と言ってもしょうがない気がします。なので、そんなことは気にしないことにします。
ラムダ式もどき
function(x,y,…){return …}は、長い。Firefoxならfunction() … で書けるけれど、それでも長い。というわけで、linq.jsでは文字列でラムダ式風に記述出来るようにしています。
var CreateLambda = function(expression) { if (expression.indexOf("=>") == -1) { return new Function("$", "return " + expression); } else { var expr = expression.match(/^[(\s]*([^()]*?)[)\s]*=>(.*)/); return new Function(expr[1], "return " + expr[2]); } } var lambda = CreateLambda("i=>i*i"); var r = lambda(3); // 9 E.Range(1,10).Where("$%2==0").Select("$*$") // linq.jsではこんな感じで書ける
「引数=>式」で文字列を与えます。引数が一つ以下の場合は=>を省略出来ると同時に、$が引数の値として使えるようになっています(Scalaの_とかこんな感じ、なはず)。実装は見た通り非常に単純で文字列分解してnew Functionに渡して関数作ってるだけ。これの難点は、クロージャにならないので、変数のキャプチャが出来ないことです。まあ、そういう時は諦めて無名関数作ってください。
まとめ
filterやmapやreduceが使えて、distinct(重複除去、いわゆるuniq)が使えて、遅延評価だったり、selectやwhereを何段もポコポコと追加出来るわけです。linq.jsはシンプルなライブラリです。派手な機能は一切ありません。ただ列挙して処理するメソッドしかありません。DOMなど一切触りません(DOMの列挙自体は可能なので、DOMノードを流してフィルタリングしたり加工したり、というのは有益でしょう)。ただ、それ故に、使い道は無限大です。
微妙に更新止まってます、が、やる気はあります!まずはWSH対応から!と言いたいのですが、現在は何故かJava移植の制作を進めています。Javaにも素晴らしいLinq to Objectsの世界を、忠実移植で。というわけなのですが、これも先月ぐらいからやるやる詐欺中。中身は完全に出来上がっていて現在テストとJavaDoc書き中。今月中にはリリースしたい、ですね。先月も同じこと言ってましたが、まあ、着々と鈍足ながらも進んでいるので、近いうちにはお見せできるはずです。
ともあれ、Linq to Objectsは大変素晴らしいので、C#な人はガンガン使って欲しいし、JavaScriptの人はlinq.jsを試して欲しいし、Javaな人はもう少し待ってください。私は、ええと、このサイトのC#カテゴリのほとんどがLinq絡みです、ひたすらに使い倒して、有用な使い方を紹介していけたらと思っています。
RxJS用IntelliSense生成プログラム(と、VisualStudioのJavaScript用vsdocの書き方)
- C# JavaScript Rx - 10.03/26
先日、Reactive Extensions for JavaScript(RxJS)の記事を書いたわけですが、触っていて困るのは、どのメソッドが使えるの?ということ。リファレンスもない中で、C#版の記憶を頼りに手打ちでメソッド名を探るなんて、無理。ましてやそんな状況じゃあ人に薦められないよ!というわけで、必要なのはIntelliSense(入力補完)です。rx.jsはある。rx-vsdoc.jsはない。ないものは、作ればいいぢゃない。そこで諦めてメソッド全部暗記してやるぜ、とか思うのはどうかしてる。諦めたら試合終了ですよ。楽するために手間を掛けるのです<プログラマの三大美徳。というわけで、作りました。
- RxVSDocGenerator.zip (source and binary)
vsdocファイルをそのまま配布するのはライセンスの問題が出そうなので、生成プログラムを配布します。手作業じゃなく自動生成で作ったので(面倒くさくて手作業なんてやってられるか!)。Rxをインストールしたフォルダ(デフォルトだとProgramFiles\Microsoft Reactive Extensions)のScriptSharpフォルダの下のRxJS.dllとRxJS.xmlを、生成プログラムと同じ階層に置いて実行すると、rx-vsdoc.jsが生成されます。
利用するにはvsdoc対応パッチをあてたVisualStudio 2008 SP1(VS2010はパッチをあてなくても対応しています)を用意して、rx.jsと同じ階層に置くだけです。HTMLで使う場合はscript src=”rx.js”で読み込むだけ、独立したjsファイルで補完を使う場合は、行頭に/// <reference path=”rx.js” />と記述すれば補完が読み込まれます。この辺は、以前に最もタメになる「初心者用言語」はVisualStudio(言語?)という記事を書いたときに補完愛してる愛してる愛してると連呼しながら解説してました。jQueryのドットで補完効かせながらのメソッドチェーンは気持ちイイんだって!
折角作ったので海外の人にも利用してもらおうと、また、標準でvsdocも同梱して欲しいと訴えるためにもとRxの公式フォーラムでスレ立てたけど、奇怪英語(機械翻訳英語)が恥ずかしいです……。ニュアンスをミジンコほどにも伝えられた気がしません。英語読めない書けないプログラマなんて小学生までだよねー、とかいう自己啓発系ブログ記事は山のようにあるわけですが、ふん、どうせ英語読めませんよ書けませんよ、ぐぐる先生による機械翻訳さえ超進化してくれれば小学生でも生きていけるもん!(ちなみに私はヤフー翻訳派です)
MIX10の発表によるとMicrosoftはAjax関連はjQueryに一本化する、ということで、C#erもますますJavaScriptを書かなければならないシーンは増えていきそうなので、せっかくなのでJavaScript用のvsdocの書き方を解説します。ついでに、LinqまみれなRxVSDocGeneratorのコードの解説も若干します。Mono.Cecil.dll使ってたりするんですよー(モジュールの参照用にしか使っていないので些かオーバースペック)。
C#と比較するJavaScriptの構造
JavaScriptは割とヒネクレた書き方が幾らでも出来るわけですが、VisualStudioの入力補完は、素の状態だと素直に書かないとついてきてくれません。というわけで素直に書きましょう。素直に書けば素直なIntelliSenseが手に入ります。以下、10秒でわかるC#とJavaScriptとの構造比較。
// 名前空間、もしくは静的クラス Rx = {} Rx.Disposable = {} // クラス(コンストラクタ) Rx.Observable = function(){ } // 継承 Rx.AsyncSubject.prototype = new Rx.Observable; // 静的フィールド Rx.Disposable.Empty = null; // インスタンスフィールド Rx.GroupedObservable.prototype.Key = null; // 静的メソッド Rx.Observable.Range = function(start, count, scheduler){ } // インスタンスメソッド Rx.Observable.prototype.Select = function(selector){ }
ヒネクレたことさえしなければ、JavaScriptはシンプルです。オブジェクトとファンクションしか存在しない。シンプルさ故の制限を回避するために、また、幾らでも回避可能なためバッドノウハウのようなヒネクレた手段が大量に溢れていて、シンプルさとは無縁の奇怪な代物と成り果てていますが(JSはシンプルだよ、初心者にお薦め!というそばからクロージャがどうのapplyがどうのと言うのはどうなのよ、勿論、その柔軟さもまたJSの魅力の一つだとは思いますが、それをシンプルとは言わない)、素直に見れば、シンプルです。
そしてまあ、C#と割と似てます。構文似てるし。単一継承だし。prototypeに後からメソッドを足せるのは拡張メソッドのよう。違いは、privateはないしプロパティはないしインターフェイスはないしオーバーロードもない(但し引数は省略可能)、いつでも簡単に全てが変更可能(不注意に扱えばすぐ構造をぶっ壊せる←だからライブラリの衝突の問題がある)。といった問題は、若干ヒネクレればある程度は回避可能です、privateとか。でも、素直に書いた方が良いと思います。JavaScriptにprivateはない。と、割り切ってしまうと非常に楽になれます。良いか悪いかはともかく。
ただ、素直に書こうと、素のJavaScriptでは、補完は簡単に限界がきます。例えば以下のコード。
var func = function(bool) { return (bool) ? "string" : [4, 5, 2, 3, 1]; } var b = Math.random() < 0.5; // true or false func(b).toUpperCase(); func(b).sort(); // どちらかで必ずエラー
引数の型が自由なら、戻り値の型もまた自由。じゃあどうするの?というと、どうにもなりません。戻り値の型はなるべく統一しましょう、IntelliSenseに優しくするために。これもまた素直の一つでしょうか、さてはて。
vsdoc.js入門
素直に素直に、と言ったところで何処かで破綻する。だいたい、JavaScriptの言語としての柔軟さを生かさないでどうする!という話は尤もなこと。そこで、VisualStudioはJavaScriptの入力補完に気の利いた仕組みを用意しています。ファイル名-vsdoc.jsが同階層にある場合、vsdoc.jsの構造を利用して入力補完を行います。なので、オリジナルに手を加える事なく補完を利用することが出来ますし、また、オリジナルが補完生成し辛い構造をしていても問題はありません。最終的にユーザーが利用するPublicの構造というのは、上で書いた素直なJavaScriptで再現出来るわけなので、それで構築すればいいだけです。勿論、別箇に構造を作成するというのは手間が増えるので、可能な限りは素直な構造にしておいたほうが無難です。
「IntelliSenseに候補が出ないものは存在しないに等しい」。これは.NETのクラスライブラリ設計という本に書かれている言葉なのですが(神本なので未読の人は絶対購入しましょう)、候補を出しさえしなければ、利用者にとって存在しないようなものに見えます。実際はpublicであっても、補完候補から削ってしまえばprivateに見える。擬似的なprivateの表現としては、中々スマートではないですか?
そんなvsdocですが、ちゃんとしたドキュメントが今ひとつ見あたらないので、jQuery用のvsdocを参考にすると良いでしょう。色々な属性が用意されているようですが、実際の入力補完に利用されるものは少ししかありません。optional属性なんて、オーバーロード的なものの表現に使えるのでは?と期待をかけたのですがそんなことはなくて、IntelliSense用には動作しませんでした。よって、summary, param, returnsだけ抑えておけば良いです。
var sum = function(x, y) { /// <summary>足し算</summary> /// <param type='Number' name='x'>引数1</param> /// <param type='Number' name='y'>引数2</param> /// <returns type='Number'></returns> } Rx.Disposable.Empty = new IDisposable;
C#と違ってfunctionの「下」にドキュメントコメントを書きます。また、ドキュメントコメントを使う場合は、関数本体はあってもなくても無視されるので、不要です。なお、ドキュメントコメントは-vsdoc.jsだけで有効なわけではなく、普通のjsファイルでも有効です。summary, paramは面倒くさかったら書かなくてもそんなに害はなさそうですが(但し引数違いのオーバーロードがある場合はsummaryで伝えてあげると使う人に優しい)、returns typeだけは欠かさず書いておきたい。これを書いておくと戻り値の型がVisualStudioに認識されるので、IntelliSenseを途絶さず利用できます。
制限事項としては関数のみにドキュメントコメントを埋め込むことが出来ます。vsdocを作る際にフィールドの型も認識させたい場合は、ダミーの変数を与えてあげればOK。
ジェネレータの解説
と、いった基本を抑えておけば、どんなライブラリに対してもvsdocを作れるね!じゃあ、rx-vsdoc.jsも手作業で作ろうか。と、思った時もありました。構造自体はjs自体をダンプでなんとかなる(と、いいなあ)だろうし、summaryやparamは諦めるとしてreturns typeだけを手作業で書くなら、どうせほとんどRx.Observableなので手間もそんなでもない。けど、rx.jsは難読化されていて引数の名前がイミフ、例えばRx.Observable.Range(k0, l0, m0)というんじゃ苦しい……。やっぱsummaryもparamも必要。でもどうすれば……?
そこで、インストールディレクトリを見てみるとScriptSharpなんてフォルダがあるんですよ。そう、RxJSはScript#でC#コードから生成されたJavaScriptライブラリだったのだよ、ナンダッテー!そして、ScriptSharp用のRxJS.dllには当然、完全なクラス構造と、引数の名前と型が保存されているし、更にはsummary用のxmlも用意されていた。つまり、ここからrx-vsdoc.jsを生成すればいいわけです。
というわけでリフレクション。型情報を取るため、早速Assembly.LoadFrom(”RxJS.dll”).GetTypes()とすると、落ちる。はあ、ScriptSharpのdllに依存してるのでそっちもないとダメなのね。というわけでScriptSharpのdllを幾つか参照に加えると、なんかうまく動かせない。ScriptSharpのdllはmscorlibの代替となってる(JSに変換可能なもののみに制限を加えてる?)から、一緒には動かせないとかそんな感じなのかなー、よくわからないけどとにかく動かせない、諦める。南無。無念。
そもそもLoadするからダメなわけで、Loadしなくていいよ、型情報だけ取れればそれでいいんだって。でも標準ライブラリには、それを可能にするのはないっぽい。けど、Monoにはあった。Cecil - Mono。参照も書き換えも出来るようですが、今回は参照のみで。色々出来そうなので、いつかもう少し触ってみたいですね。私は今回はじめてMono.Cecil.dllを使ったのですが、リファレンスの類も見てない(あるのか知らない)し、チュートリアルの類も見てない(ていうか日本語の情報がない)。でも、IntelliSenseでドット打ってれば何とかなりました。しっかりした構造とちゃんとしたメソッド名とIntelliSenseがあれば、リファレンスがなくても問題なく使えるわけです。すばらしきこのせかい!
Mono.Cecil
型情報を取ってくるだけなら簡単で、というかSystem.Reflectionと大して変わりません。
var rxjsTypes = AssemblyFactory.GetAssembly("RxJS.dll") .MainModule.Types.Cast<TypeDefinition>()
TypeDefinition, MethodDefinition, ParameterDefinitionといったのが個の要素。そして、対応するコレクションHogeCollectionが用意されています。HogeCollectionは残念ながらジェネリックではないため、Linqに流すためにはCastが必要になります。今回はParameterDefinitionCollectionのSelectを多用することが多かったので、Cast無しで使えるよう拡張メソッドを定義しちゃいました。
static IEnumerable<T> Select<T>(this ParameterDefinitionCollection source, Func<ParameterDefinition, T> selector) { return source.Cast<ParameterDefinition>().Select(selector); }
この手のレガシーなコレクションに対するアドホックな対応は、例えば正規表現のMatchCollectionなんかにも使えそうです(と、いった発想の元ネタはAchiralから)
テンプレート置換
必要なJSの構造は上のほうで書いた通り決まったパターンがあるので、雛形を元に置換するのが楽。テンプレートエンジン、なんていう大仰なものは必要ないけれど、string.Formatでも{5}とか出てくると引数の管理が面倒だし、順番の変更にも弱い。なので簡易置換用の拡張メソッドを用意してみました。
static string TemplateReplace(this string template, object replacement) { var dict = replacement.GetType().GetProperties() .ToDictionary(pi => pi.Name, pi => pi.GetValue(replacement, null).ToString()); return Regex.Replace(template, "{(" + string.Join("|", dict.Select(kvp => Regex.Escape(kvp.Key)).ToArray()) + ")}", m => dict[m.Groups[1].Value]); }
オブジェクトを渡すと、{プロパティ名}の部分をプロパティの値に置換します。オブジェクトなのでクラスインスタンスでもいいのですが、匿名型も使えます。例えば
const string classTemplate = @" {FullName} = function({Parameters}) { /// <summary>{Summary}</summary> {Param} }"; var r = classTemplate.TemplateReplace(new { FullName = "Rx.Notification", Parameters = "kind", Summary = "Represents a notification to an observer.", Param = " /// <param type='String' name='kind'></param>" }); Console.WriteLine(r);
割と便利。たった9行なので、ちょっと気の利いた置換が欲しいなあ、って時にササッとコピペして取り出せるのが魅力です。最近、コピペに優しいプログラミングをよく考えてる。というのはともかくとして、実際どんな風に使っているかというと、
var classes = rxjsTypes .Where(t => t.Constructors.Count > 0) .Select(t => new { t.FullName, Parameters = t.Constructors.Cast<MethodDefinition>() .Select(m => m.Parameters) .MaxBy(p => p.Count) .Select(p => p.Name) .ToJoinedString(", "), Summary = summaries[t.FullName] + " " + t.Constructors.Cast<MethodDefinition>() .OrderBy(m => m.Parameters.Count) .Select(m => m.Parameters.Select(p => p.Name).ToJoinedString(", ")) .Select((s, i) => string.Format("{0}:({1})", i + 1, s)) .ToJoinedString(", "), Param = t.Constructors.Cast<MethodDefinition>() .MaxBy(m => m.Parameters.Count) .Parameters .Select(p => string.Format(Template.Param, p.ParameterType.ToJSName(), p.Name)) .ToJoinedString(Environment.NewLine), }) .Select(a => Template.Class.TemplateReplace(a));
前段階で匿名型を生成して、最後のSelectで置換をかけてます。Select二段にしないでもいんじゃね?というとYESですが、このほうが見やすいと思うので。それにしてもリフレクションなわけで、Linqと非常に相性が良い。というか、Linqなしだと大量のforループとifで涙を流すことになりそう。なので、昔はリフレクションって結構敷居が高かったのですが、今はもうLinqでサクサクとWhereで切って捨ててSelectで繋げて繋げて、って出来るので書く分には楽チンです。これがLinq以前のC#2.0だったら、考えたくないなあ。
出力
クラス、継承、オブジェクト、メソッド、プロパティは全部バラバラに抽出しています。そして、全部IEnumerable<string>で止めています。最後にそれらをまとめて、テキストとして出力。
var vsdoc = Enumerable.Repeat(string.Format(Template.Object, RootNamespace), 1) .Concat(classes) .Concat(inheritance) .Concat(objects) .Concat(methods) .Concat(properties) .ToJoinedString(Environment.NewLine); File.WriteAllText("rx-vsdoc.js", vsdoc, Encoding.UTF8);
せっかくクエリ遅延評価にさせているので、書き出しも一度stringに貯めないでストリームで書きだせば高効率ですねー。でも、一度文字列に出した方が書くの楽なので。せいぜい1000行程度なので、ケチッても意味ないですな。
全部rxjsTypesをルートにして生成しているので、クエリ構文を使って巨大な一塊にしてみたら面白かったかな、なんて思いますが若干悪趣味な気もするのでやめておきます。そもそも、このドットだらけ、Selectだらけの時点で若干どうよ、といった趣が漂っているのは間違いない。いやいや、ドット素敵です。Linq素敵なんだって、本当に。こういうの書いてるとC#2.0と3.0は別物だろ常識的に考えて、と思わなくもない。セミコロン率は物凄く低くなりましたね……。あと、LinqとSQLを関連付けるのはそろそろやめようぜー、的な思いがふと過ぎったり。Twitterのpublic検索でlinqをキーワードに毎日眺めてるんですが、今でも割とそういう印象持ってる人多いんだなー、と。だからどうしたとかどうなるってこともないですが。
まとめ
IntelliSenseでLinqはより楽しくなる。メソッドチェインはIntelliSenseでより楽しくなる。そして、VisualStudioはJavaScriptエディタとしても優秀なので皆VisualStudio使おう!インストールが面倒?Microsoft Visual Studio 2008 Express EditionからWeb インストールをクリックするだけでオールインワンでダウンロード含めて10分ぐらいで全部やってくれる。時間がかかるというのは正しいですが、意外と面倒くさくはないんです。それと、このExpress Editionは無料です。
入力補完だけじゃなく、コード整形やデバッガ(開発環境と完全統合されているためFirebugよりもずっと使いやすい)などもあるし、ある程度は裏でインタプリタをぶん回して変数名間違いなどのエラーを補足してくれるので、IDE無しでJavaScript書くなんて、そんな苦労、しなくてもいいんだよ……。
Reactive Extensions for JavaScript
- JavaScript Rx - 10.03/18
MIX10終了しましたねー。何はともかく皆してIE9の話題ばかりで、ああ、InternetExplorerは愛されてるなあ(色々な意味で)、などというのを横目に、私にとっての最大のニュースはReactive Extensions for JavaScript(RxJS)です。Reactive Extensions(Rx)はこのサイトでもカテゴリーを作ってメソッド探訪なんてやってるぐらいに注目していたわけで、当然MIX10でJavaScript版出すという話を聞いた時からwktkが止まらなかったわけですが、全く期待を裏切らなかった!というか、こいつはヤバいですよ?
Reactive Extensionsとは何ぞや、というと、LinqというC#の関数型言語みたいなリスト処理ライブラリ(語弊ありまくり)のイベントとか非同期版です。イベントや非同期処理にたいしてmapとかfilterとかfoldとか、お馴染みなリスト操作関数が使えちゃうという、その発想はなかったわ、なライブラリです。C#版は去年の夏ぐらいにプレビュー版が出て、いまも精力的に開発が続いているのですが、今回はJavaScript移植版が出た、という話です。イベント(onclick!onclick!)や非同期(XMLHttpRequest!)ってのは、勿論C#でも大事なのですが、JavaScriptなんてそれが主役というぐらいなわけなので、C#版よりもインパクトは大きいです。何よりも、C#は言語やライブラリが強力なので別にRx使わなくてもって感じなのですが、JavaScriptは違う。あまりにも貧弱。なので、優れたイベント/非同期処理ライブラリの重要度はC#の比ではなく高い。
RxJSの面白いところはjQueryに対抗するものではなく、むしろ協調動作するように作られていることです。DOM操作はjQuery、イベントや非同期処理はRxJS、二つの強力なライブラリを組み合わせることで、JavaScriptプログラミングは次のパラダイムへ向かおうとしています。御託は、もういいですね、とりあえずサンプルを。
マウスをクリックして四角形を掴んで、マウスを動かして、放すという、ごく普通のドラッグアンドドロップ。素の JavaScriptではどうやって実装しますか?グローバルに状態管理用のオブジェクトを置いて、オフセット用の変数置いて、マウスの状態を監視して、うーん、考えたくない。あまり綺麗に出来そうにない。それがRxJSを使ってみると――
var O = Rx.Observable; // using $(function() { var doc = $(document); var area = $("#dragArea"); O.FromJQueryEvent(area, "mousedown") .Select(function(e) { var offset = $(e.target).offset(); return { X: e.pageX - offset.left, Y: e.pageY - offset.top } }) .SelectMany(function(offset) { return O.FromJQueryEvent(doc, "mousemove") .TakeUntil(O.FromJQueryEvent(doc, "mouseup")) .Select(function(e) { return { X: e.pageX - offset.X, Y: e.pageY - offset.Y }; }) }) .Subscribe(function(a) { area.css({ left: a.X, top: a.Y }) }); });
状態管理変数は使いませんし、全て一連のメソッドチェーンだけで完結します。以下解説。
Rx.Observable.FromJQueryEventは、イベントをRxオブジェクトに変換します。これはjQueryの$などと同じものだと思ってください。FromJQueryEventと同様の動作をするものにFromHtmlEventというものがあります。ほとんど同様ですが、jQueryオブジェクトを使う場合はFromJQueryEvent、getElementByIdなどで得られたネイティブの要素を使う場合はFromHtmlEventを使いましょう。基本的には、クロスブラウザ回りの面倒事を任せられるjQueryとの併用がお薦めです。
Rxオブジェクトに変換後は、多数のメソッドをjQueryのようにチェーンさせて記述していきます。どれだけ多数かというと、ここに並べてみます。
Subscribe, Select, Let, MergeObservable, Concat, Merge, Catch, OnErrorResumeNext, Zip, CombineLatest, Switch, TakeUntil, SkipUntil, Scan1, Scan, Finally, Do, Where, Take, GroupBy, TakeWhile, SkipWhile, Skip, SelectMany, TimeInterval, RemoveInterval, Timestamp, RemoveTimestamp, Materialize, Dematerialize, AsObservable, Delay, Throttle, Timeout, Sample, Repeat, Retry, BufferWithTime, BufferWithCount, StartWith, DistinctUntilChanged, Publish, Prune, Replay
何も一気に全部を知る必要はないので、あまり圧倒されずに、少しずつ学んでいければいいかな? 私もちょいちょいとブログ記事で紹介していきたいと思っています(やるやる詐欺ばかりですが……)。そうそう、ソースコードが圧縮されていて実際の名称は不明なのでRxオブジェクトと呼んでますが、それであってるかは今のところ謎です。C#ではIObservableなので、IObservableでいいかな、という気はしますが。
イベント as リスト
もう少しFromJQueryEventの動きを考えますか。イベントが発生すると、後ろのメソッドにイベントオブジェクトが渡されます。再度クリックすると、またイベントが発生しイベントオブジェクトが渡されます。再度(以下略)。つまりは、[event, event, event…]。終りのない配列。無限リスト。まるでイテレータのような……。そう、ObserverパターンとIteratorパターンは同じなのだよ、ナンダッテー!よく分からない?確かに。こういう時は他の人の言葉を借りてしまおう。最近Scalaの入門記事が注目を集めました。「神は言われた。「リストあれ。」」。そうです、イベントもまた、Rxの手にかかればリストになってしまうのです。Rxは関数型言語のリスト操作のようにイベントを高階関数で処理できます。それがもたらす世界、想像するとワクワクしませんか?なお、JavaScriptで配列やDOMに対してリスト操作を行うライブラリとしてlinq.jsというがありますのでよろしくお願いします←宣伝(作ってるの私なので)。
ドラッグアンドドロップの構造
mousedownでイベントが発動しても、本当に必要なイベントはdownじゃなくてmove。なので、downはイベント発動とオフセット算出にだけ使って、実際に後ろに流す情報は別のところから得ます。そこで、mousedownとmousemoveを合流させなければなりません。シーケンス(Rxによりリストのような何かになっているので、Sequenceが言葉として適切な気がします)の結合はMergeやCombineLatest、Zipなど、用途に応じて色々あるのですが、ここは一(mousedown)から多(mousemove)の状態を作ることが可能なSelectManyを使用します。SelectManyに渡す高階関数の戻り値(Rxオブジェクト)が、更に平たくされて後ろのメソッドに渡されていくことになります。
TakeUntilは、「~まで取得する」。引数のイベントが発動されるまでシーケンスを流し、発動されたら一切流さなくなる。つまりFrom(mousemove).TakeUntil(mouseup)は、mouseupされるまでmousemoveイベントを発行するということ。驚くほど簡単にドラッグアンドドロップの構造が記述出来てしまいました。これはヤバい。Rxヤバい。簡潔すぎるだろ常識的に考えて。
Selectは多くの関数型言語やRubyなどで言うところのmapで、要素を変形して返すもの。ここではeventからclientX, clientYだけのオブジェクトを作っています。mapがあるということは、勿論filterもあります(メソッド名はWhere)。この辺はlinq.jsの解説がそのまんま適用出来ます。何故かというと、Observerパターン(RxJS)とIteratorパターン(linq.js)は(以下略)。
Subscribe
シーケンスを変形していったら、最後に登録してやる必要があります。それがSubscribe。Subscribeを呼んで、初めてイベント(mousedownなど)に関連付けられます(addEventListenerです、ようするに)。このSubscribeは感覚的にはforeachのようなもので、無名関数の第一引数に今までに変形させた変数が入っているので、それを取り出して何らかのアクションを取る。今回はstyleを弄って四角形の座標を変更してやりました。
addEventListenerということは、デタッチもあるの?というと、ありますあります。Subscribeの戻り値はvoidではなく、IDisposableオブジェクトというものになっています。この戻り値のIDisposableオブジェクトを取っておけば、デタッチさせたい時にDisposeメソッドを呼んでデタッチさせられます。
Rx.Observable
Rxオブジェクトのメソッド一覧は書きましたが、Rx.ObservableにはFromHogeHoge以外にも色々なメソッドがあるよ。イベントだけじゃなく、あらゆる方向からRxオブジェクトを作り出す驚異のメソッド群はこれだ!
Amb, Catch, Concat, Create, CreateWithDisposable, Defer, Empty, FromArray, FromDOMEvent, FromHtmlEvent, FromIEEvent, FromJQueryEvent, Generate, GenerateWithTime, Interval, Merge, Never, OnErrorResumeNext, Range, Repeat, Return, Start, Throw, Timer, ToAsync, Using, XmlHttpRequest
いっぱいありますねー。名前から想像つくものからつかないものまで。XmlHttpRequestとか興味をひくところです。そう、Rxはイベントだけではなく、非同期通信までリストに変換し統一的な操作を可能にしてしまうわけです。こっちも重要なので、後日サンプル書きます。
結論
jQueryあるからRxJSなんてイラね、というわけじゃあないんですよ!ふたり揃ってプリキュア。最初の方にも書きましたが、jQueryのDOM操作は素晴らしいのでおまかせでいいです。イベントや非同期処理はjQueryだけじゃ足りないところがあります。そこはRxJSで補います。ついでに通常のリスト操作(配列やDOM Elements)は全然足りてません。prototype.jsはあんなにイけてたのに、jQueryになってからリスト処理がシンドいですね。そこはlinq.jsです(宣伝しつこい)。これでもうJavaScriptに死角はなくなった……、勝った、HTML5時代バンザイ!(でも私はC# + Silverlight4に期待をかけるけどね)
参考リンク
Rxチームの一員であるJeffrey van Goghによる、Rxをインストールしたディレクトリに置いてある、TimeFilesサンプルの解説。forループがダサいのでlinq.jsを使って書き直してTwitterに流したんですが、そうしたらJeffrey van Goghに言及してもらった!。これは嬉しい。
Matthew PodwysockiによるRxJSの解説シリーズ。From(mousemove).TakeUntil(mouseup)のネタ元はここだったりして。最高にクール!
JavaScriptでString.Format的な超簡易テンプレート置換
- JavaScript linq.js - 09.09/18
// String.Format的な超簡易テンプレート置換関数 var Format = function(template, replacement) { if (typeof replacement != "object") // 可変長引数時はreplacementを詰め替え { replacement = Array.prototype.slice.call(arguments, 1); } return template.replace(/\{(.+?)\}/g, function(m, c) { return (replacement[c] != null) ? replacement[c] : m }); } // 例。可変長引数渡しでも配列渡しでもオブジェクト渡しでも可。 var case1 = Format("<div id={0}>{1}</div>", "あいでぃ", "要素"); var case2 = Format("<div id={0}>{1}</div>", ["あいでぃ", "要素"]); var case3 = Format("<div id={ID}>{ELEM}</div>", { ID: "あいでぃ", ELEM: "要素" })
.NET FrameworkのString.Formatは文字を連結するのに、非常にお手軽で良いです。というわけでJavaScriptでもそれをやる。ついでにテンプレート置換風にオブジェクト渡しにも対応させる。単純な置換時は数字で、長ったらしい置換時はオブジェクトで。両方に対応させなければ、詰め替えが必要ないので正規表現でカカッと一行なんですねえ。詰め替えも別にslice.callで一発だし。以前にlinq.jsを絡めてgdgdとやってたのですが、二度もね!、あんなにgdgdやらずとも、もんのすごーく単純明快に書ける。無理やり使おうとして、無駄に複雑になるのはイクない。
と、恥ずかしくなったので今回載せました。あと、JavaScriptは文字連結面倒くせー、って時にササッとコピペで取り出して使いたい時のために(笑) ちゃんとDateTime等も含めたフォーマット変換に対応させるとか、テンプレートだったらちゃんとテンプレートエンジンな感じでforやifも動くように、とかの話は知りません。
そういえば、置換部分の関数ですけど、最初は格好つけて「return replacement[c] || m」って書いたんですが、これだとマッチがハッシュ内に見つからなかった場合(undefinedになってる)だけでなく、 空文字列の場合もfalse扱いになってしまってダメなんですね。C#の??のように使いたいのですが、例えば数字だと「var i = 0 || 3」だったら3になるしで使いづらい。というわけで、結局==nullばかり使うことになる。===undefinedって書けって話でもありますが、まあ、==nullのほうが色々考えなくて済むから楽で。
無限リピートの幸福
- C# JavaScript linq.js - 09.09/09
Reactive Frameworkが、結構に無限リピートな感じなので、関連してC# Linqでどう書くにあった13日金曜日問題を今更書いてみた。n番煎じ。
// 今日から2013年12月31日までの、13日の金曜日とその総数を表示してください。 // 「今日」を無限リピートという方針で書いてみたりして(総数は省略) // 利点はTodayを変数として外側に定義する必要が無くLinq内に閉じ込められる // Toを求めるのに足したり引いたりする必要がなく自然に書ける、の二つかしらん // 「まで」という問いに対してTakeWhileで解答するのは自然で良いと思う Enumerable.Repeat(DateTime.Now, int.MaxValue) .Select((d, i) => d.AddDays(i)) .TakeWhile(d => d.Year < 2014) .Where(d => (d.DayOfWeek == DayOfWeek.Friday) && (d.Day == 13)) .ToList() .ForEach(d => Console.WriteLine(d.ToShortDateString()));
TakeWhileが好きです。問題文に対して、自然に解答出来るような気がするので。「今日から(Repeat)」「2013年12月31日までの(TakeWhile)」「13日の金曜日(Where)」。実に自然に記述できる。いやまあ、Repeatが直感的かというと結構微妙なところではありますが。Rangeでfrom,toのほうが自然だろ常識的に考えて、というのも確かなんですけど、Rangeだとtoを作るのに計算式が必要ってのが、ちょっと違うかな、と。
Pizza (programming language)のexampleにもあるような、Streamを始めとして何かを無限リピートしてTakeWhileで終了条件を設定、というのはパターンとして結構幅広く使える、と思う。ある種のデザインパターン。イディオムイディオム。参考リンクはC# 3.0 と while(true) と Iterator - NyaRuRuの日記この辺り。
例えばVS2010から搭載されるEnumerable.Zipや、あとCycleを定義してみる。
// この二つを混ぜ合わす(VS2010で搭載されるZip関数) var seq1 = Enumerable.Range(1, 10); var seq2 = Enumerable.Range(10, 10); Enumerable.Repeat(new { e1 = seq1.GetEnumerator(), e2 = seq2.GetEnumerator() }, int.MaxValue) .TakeWhile(t => t.e1.MoveNext() && t.e2.MoveNext()) .Select(t => t.e1.Current + t.e2.Current); // ここがZipのSelectorの部分 // foo,bar,hoge,foo,bar,hogeを無限に繰り返す var elements = new[] { "foo", "bar", "hoge" }; var cycle = Enumerable.Repeat(elements, int.MaxValue).SelectMany(ar => ar);
Linqのお陰でかつてない勢いでint.MaxValueを使っているこの頃。Repeatも万能ですねえ。いやまあ、もう素直にAchiral使えよって話なんですが、標準メソッドのみで粘るのも面白くて。そういえばでついでなのでlinq.jsでもやってみた。
// JavaScriptはAddDaysがないので副作用全開でTodayを // setHours(24)で翌日にしてしまう、という方針でやってみた E.Repeat(new Date()) .Do("$.setHours(24)") .TakeWhile("$.getFullYear() < 2014") .Where("$.getDay() == 5 && $.getDate() == 13") .ForEach("alert($)");
DoはReactive Frameworkにもありました。副作用を加えた上で素通しするメソッド。副作用は嫌なものです。汚いです。何が嫌かというと、動作を考えるのに見る範囲を広げなきゃいかんところかなあ。そしてLinqの何がいいかというと、見る範囲が物凄く限定される(ラムダ式一文だけを見ればいい)と思っている。だからLinq内でクロージャ(というか外部の変数をキャプチャして使う)もあんま好ましくないし、C#クエリ構文のletも好きじゃない。なるべくなら使いたくない。長文耐性なのは分かるけれど、カッコやインデントがなくてスマートだけれど、その分だけスコープが不明瞭になるという側面が否めない。まあ、letが必要なシチュエーションをSelectManyでやると、大抵はもっと奇怪になるのですけど。
んでまあ、この場合だとAddDaysのかわりにnew Date(year,month,day)で新しいのを作れば副作用なくSelectが使えるわけですが、ありきたりで面白くないと思ったので別な方向に走ってみた。というか、無限リピートは、無限リピートする何かに対して副作用全開で操作を加え続ける、という形の方が面白いというか実用的というか普通だとは思う。冒頭の例みたいなやつだと、別にRangeでよくね?って感じですし。Haskellじゃないんだから、潔癖症にならずに、副作用といかに楽しくお付き合いするかが大事なのですかね。
あ、ちなみに$は引数が一つの場合の省略記法です。こういった機能はScalaにもある。引数が一つのみの場合が大半なので、記述がグッと縮まるし、何よりも引数名を付ける必要がないのが嬉しい。C#でも使えるようになると嬉しいなあ、とずっと思ってるんですが中々どうして無理なんですかねえ、残念。
JavaScriptで要素追加するやり方
- JavaScript linq.js - 09.08/27
ド素人がjQueryとprototype.jsではどう書くのかな、と思っただけです。メジャーな両者ですが実はまともに使ったことがないのです。困ったことに。しょうがないので見よう見まねで書く。
<!-- このselectにoptionを一個追加する --> <select id="selectID"> <option value="1">hugahuga</option> </select> <script type="text/javascript"> // 素のJavaScriptその1(古臭いというか微妙な……) var option = new Option("要素", "属性"); var select = document.getElementById("selectID"); select.options[select.options.length] = option; // 素のJavaScriptその2(これはダルい) var option = document.createElement("option"); option.setAttribute("value", "属性"); option.appendChild(document.createTextNode("要素")); document.getElementById("selectID").appendChild(option); // みんな大好きjQuery $("<option>").attr({ value: "属性" }).text("要素").appendTo("#selectID"); // 何だかんだで好きなprototype.js var option = new Element("option", { value: "属性" }).update("要素"); $("selectID").insert(option); // linq.js + linq.xml.jsの関数型構築 var option = X.Elem("option", X.Attr("value", "属性"), "要素"); X.ID("selectID").Add(option); </script>
素のJavaScriptその1はねーよ、というわけで、その2をいかにスマートにやるかという話。だと思う。jQueryのappendToが合理的というか便利なのは分かるけど、キモく感じてしまう。んで、どれが好きかっていたら、当然自分で作ってるlinq.jsのが一番好きですよ(笑)
// linq.js + linq.xml.js var options = E.RangeTo(1, 12).Select(function(i) { return X.Elem("option", X.Attr("value", i), i + "月"); }); X.ID("selectID").Add(options); // prototype.js var options = $R(1, 12, false).map(function(i) { return new Element("option", { value: i }).update(i + "月"); }); var elem = $("selectID"); options.each(function(e) { elem.insert(e) });
X.Elem()もAdd()もLinqオブジェクト/可変長配列を受け取れるので、まとめてドバーっと追加が結構楽かな、と思います。eachとかじゃなく、そのまんま追加出来るってのが大事。上の例だと、prototype.jsではmapでoptionsを作らずそのまんまeachでinsertしちゃえばいいぢゃん、というのはそのとーりなんですが(2回もループ回ることになるしね、あ、linq.jsのは遅延評価しているのでループはAddで呼び出される時の1回しか回りません)、配列(的なもの)が既にある状態ってのは、結構ありますよね?
と、何故か突然アピールしてますがlinq.xml.jsは作りかけで放置しているので足りない関数がいっぱいあるんですけどね!
WSH用にCOMのIntelliSenseを自動生成する
- C# JavaScript linq.js - 09.08/24
本題、の前にJavaScript用のIntelliSenseの作成方法について。以前、linq.jsにIntelliSense用ファイルを追加した時に利用法を書きましたが、今回はvsdocの作成方法を、ざっと書きます。まずVS2008 SP1とパッチを適用する。hoge.jsに対しhoge-vsdoc.jsを用意するとhoge.jsのかわりにhoge-vsdoc.jsがIntelliSense用に読み込まれる。IntelliSenseでVSが見ているのはメソッド名と引数だけなので、コードの中身はなくてもいい。例えば本来は存在するがprivateメソッドのつもりのものは、vsdoc.js側に書かなければIntelliSenseには表示されない。C#と同様に関数にはXMLドキュメントコメントを記述することが出来る。記述箇所はC#とは異なり開始の波括弧の直下。ドキュメントコメントのタグで現在機能しているものはsummary, params, returnsのみ。特に重要なのはreturns typeで、これにprototypeが存在する関数(ようするにクラスですなー)を指定することで戻り値の型をVSに認識させ、IntelliSenseを働かせることが出来る。ちなみに、ただのオブジェクトだと認識してくれない。
function Hoge(aaa, bbb) { /// <summary>hogehogehogehoge</summary> /// <param name="aaa" type="String">a!a!a!a!a!</param> /// <param name="bbb" type="Optional:Boolean">b?b?b?b?b?</param> /// <returns type="Number"></returns> }
基本はこんな感じ。引数の省略や、型が目で見て分かるので大分書きやすくなります。 で、いつぞやかに作成中とか言っていた通りにlinq.jsをWSH対応させようとしているわけですが、IntelliSense書きの量が思ったよりも膨大なわけです。実は最初は普通に手書きしてました。一応勉強も兼ねて書写みたいなノリで。ですが、あまりにも手間かかりすぎるので自動生成することにしました。こんなの人力でやるもんじゃないですよ。微調整はどちらにせよ必要なのですか、大枠だけでも書きだされていると物凄く楽になる。
static void Main(string[] args) { // add reference and replace dll path var assembly = Assembly.LoadFrom(@"Interop.IWshRuntimeLibrary.dll"); var types = assembly.GetTypes(); var enums = types .Where(t => t.IsEnum) .OrderBy(t => t.Name) .Select(t => string.Format("{0}: \r\n{{\r\n{1}\r\n}}", t.Name, Enum.GetNames(t).Select(s => string.Format("\t{0}: {1}", s, (int)Enum.Parse(t, s))).Join(",\r\n"))); var classes = types .Where(t => t.IsClass) .Select(type => { var bindingFlag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; var properties = type.GetProperties(bindingFlag) .OrderBy(pi => pi.Name) // .Select(pi => string.Format("{0}: {1}", pi.Name, pi.PropertyType.Name .Select(pi => string.Format("{0}: null", pi.Name)); var methods = type.GetMethods(bindingFlag) .Where(mi => !mi.IsSpecialName && mi.Name != "GetEnumerator") .Select(mi => new { mi.Name, Parameters = mi.GetParameters(), ReturnType = mi.ReturnType.Name }) .OrderBy(t => t.Name) .Select(t => string.Format("{0}: function({1})\r\n{{\r\n{2}\t/// <returns type=\"{3}\"></returns>\r\n}}", t.Name, t.Parameters.Select(pi => pi.Name).Join(", "), t.Parameters.Select(pi => string.Format("\t/// <param name=\"{0}\" type=\"{1}{2}\"></param>\r\n", pi.Name, (pi.IsOptional) ? "Optional:" : "", pi.ParameterType.Name.Replace("Void", "void").Replace("Int32", "Number"))) .Join(""), t.ReturnType.Replace("Void", "void").Replace("Int32", "Number"))); var result = properties.Concat(methods).Join(",\r\n"); return string.Format("{0} = function() {{ }}\r\n{0}.prototype =\r\n{{\r\n{1}\r\n}}", Regex.Replace(type.Name, "Class$", ""), result.Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n")); }); var name = assembly.GetName().Name.Split('.').Last(); File.WriteAllText(name + "_enum.js", string.Format("{0}Enum =\r\n{{\r\n{1}\r\n}}", name, enums.Join(",\r\n").Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n")), Encoding.UTF8); File.WriteAllText(name + "_class.js", classes.Join("\r\n\r\n"), Encoding.UTF8); } static string Join<T>(this IEnumerable<T> source, string separator) { var index = 0; return source.Aggregate(new StringBuilder(), (sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o)) .ToString(); }
参照設定で対象のCOMを読み込んで、一旦ビルド。生成されてるInterop.hoge.dllを読み込んで解析、という流れをとってみました。もっとマシなやり方があれば教えてください。コードは、んーと、string.Formatがクドくて見難いですね! 最初はVSのフォーマッタに後で手動でかければいいや、と思ってたのを、出力状態でちゃんとフォーマットされてるようにと継ぎ接ぎしてたら酷いことに。一回文字列にしたものを改行でバラして再生成とか笑えない。こういうの、HTML/XMLが対象なら何も考えなくてもLinq to Xmlで綺麗に書けるのになあ……。 それ以外はまぁ、こんなものかしらんって感じでしょうか? リフレクションが絡むとLinq大活躍。Selectがネストしまくるのでクエリ構文の出番はない。リフレクションは掘り進める都合上、ネストが深くなるのでLinqがなかったら、と思うとゾッとします。最近はforの二重ループですらウエエ、とか思ってしまうので。
IWshRuntimeLibraryEnum = { IOMode: { ForReading: 1, ForWriting: 2, ForAppending: 8 } } Folder = function() { } Folder.prototype = { Name: null, ParentFolder: null, Drive: null, CreateTextFile: function(FileName, Overwrite, Unicode) { /// <param name="FileName" type="String"></param> /// <param name="Overwrite" type="Optional:Boolean"></param> /// <param name="Unicode" type="Optional:Boolean"></param> /// <returns type="TextStream"></returns> } }
一部抜粋ですが、こんなようなデータが出力されます。プロパティが全部nullなのは、ここに未指定のものが記述されているとIntelliSenseがエラー起こしてしまうから。例えばDrive: new Drive()で戻り値の型指定が可能といえば可能なのですが、出現位置の上下が問題になってくるので結構面倒くさい。ようはFolderよりも上にDriveがあれば関数が存在するので大丈夫だけど、下にあれば存在しないのでエラー。当たり前といえば当たり前なのですが、ダミーでのIntelliSense作りとしては面倒な問題でして。 この辺は素直に諦めて全部nullで型連鎖を止めてしまうのがお気楽といえばお気楽。あと、Dateはnew Date()にしてもDate.prototypeにしても型指定出来なかったりする問題もある。これは今のところ対処不能。
そんなわけで、linq.js + WSHはわりと順調に作成中なので期待しないでも待っててください。IntelliSenseというだけじゃなく、JScriptではバイト配列が扱いにくいので、扱いやすく出来るような補助メソッドを用意したりとか色々やってたらいつになっても終わらないー。例としてWSHでMD5を作る、とか。.NET Frameworkを通しているのでSHA1でも何でもいけます。これでWSHでwsse認証通してAtomAPIで投稿、とか出来ますね。まあ、もうC#でいいぢゃん、って気がかなりしなくもないですけど、うーん。C#4.0からdynamicが追加されるからCOM連携も楽になるしねえ。ただサイドバーガジェットに使うとか、JavaScriptの用途はまだまだあるもん!(これもSilverlightでやれば?って話がなくもないので、うーん)
linq.js :: Next
- JavaScript linq.js - 09.08/02
音沙汰のないlinq.jsなんですが、現在はWindows Script Host対応を進めています。対応といっても数カ所書き換えるだけなんですけど、それに合わせてWSH用のラッパーライブラリを書いているので、それに時間を取られてる感じです。基本的にはほんと薄いラッパーで、列挙処理がlinq.jsに渡せるのでそっちで好きに処理してね、という方向なので機能面での補助は一切なく別にすぐ出来上がるというかもうほとんど出来てるんですが、IntelliSenseを聞かせるためのvs-doc書きに物凄く時間喰われています。とにかくIntelliSense命な私は、IntelliSenseが動かないものなんて書きたくない!なければ自分でIntelliSenseを書く!という意味不明な方向で頑張ってます。
画像の一枚目は一週間前のものなので、現在はW.ToLinqは廃止して、E.Fromで動くようになってます。何のこっちゃ。
このご時世、WSHなんて下火、これからはPowerShellだよねー、って感じですが、それでも私はWSHで頑張る! WSHでLinq書けるのかゴルァ、を合い言葉に。JavaScript好きだし。まあ、素のJScriptだとEnumeratorを被せなきゃいけなくて列挙処理がゴミで使う気になれないのは確かなのですが、そこをlinq.jsがあれば何とか出来るわけなので、全然WSHは現役で行ける、Windows7時代でも全然行ける、と思います、思いたいです。まあ、あとWindowsサイドバーガジェット(デスクトップガジェット)にも使えるので、もうちっと踏ん張っていきたいな、というところ。Web系で頑張るのは無意味なのでニッチを狙いだしたとかそういうことではありま、す。
linq.js ver 1.3.0.2 - CascadeDepthFirst/BreadthFirst
- JavaScript linq.js - 09.06/15
linq.jsにCascadeDepthFirst, CascadeBreadthFirstを追加しました。ちょっと分かり辛いのですが非常に強力です。深さ優先/幅優先で多段SelectManyをかけていくようなイメージ。説明しづらいので例をどうぞ。
<div id="tree">Root <div>Spine <div>Neck <div>Head</div></div> <div>RClavicle <div>RUpperArm <div>RLowerArm <div>RHand</div></div></div></div> <div>LClavicle <div>LUpperArm <div>LLowerArm <div>Hand</div></div></div></div></div> <div>RHip <div>RUpperLeg <div>RLowerLeg <div>RFoot</div></div></div></div> <div>LHip <div>LUpperLeg <div>LLowerLeg <div>LFoot</div></div></div></div></div>
こんなHTML、というかツリーに色々と操作することにします。例えばinnerTextのように、深さ不明のchildNodesを全て掘ってTextNodeだけを取り出して値を連結するとしたら、通常は再帰を使いますよね。でも、CascadeDepthFirstを使えば
var root = document.getElementById("tree"); var innerText = E.Make(root) .CascadeDepthFirst("$.childNodes") .Where("$.nodeType == 3") .Select("$.nodeValue") .ToString();
こんな感じに書けます。rootから深さ優先探索で子ノードを全取得、そのうちnodeTypeが3のもの(TextNode)のnodeValueを文字列連結。宣言的に、再帰よりも分かりやすく書けます。何より後段で豊富なLinqメソッドを使って値を操作していけるのが利点になります。今回から新しく追加されたMake(hoge)というのはRepeat(hoge,1)と等しい。Fromを用いるとオブジェクトはKeyValuePairに、文字列は一文字ずつのシーケンスに化けてしまうので、今回からこのメソッドを用意しました。
実のところMakeもCascadeDepthFirstもC# 3.0 Supplemental Library: Achiral - NyaRuRuの日記のパクりです(AchiralではMakeはMake.Sequence)。ぶっちゃけ今までも勝手に散々パクッているので(ごめんなさい、ありがとうございます。ライセンス的にアレっぽいので、どこかに書いておかないと……) むしろ何で今更追加なのかというと、あまりにも露骨すぎて劣化コピーを入れるのは失礼に思っていて。結局入れてしまったけれど。そんなわけで応用的なものを一つ。引き続き↑のDOMツリーを使って……
var root = document.getElementById("tree"); E.From(root.childNodes) .Select(function(child) { return { child: child, parent: root} }) // 外部の値を取りこむ時は文字列ラムダ式は使えません .CascadeDepthFirst(function(pair) // 中を入れ子で親の値を参照したいので文字列ラムダ式は不可 { return E.From(pair.child.childNodes) .Select(function(child) { return { child: child, parent: pair.child} }); }, "v,n=>{value:v,nestLevel:n}") // CascadeDepth/BreadthFirstの第二引数はネストレベルを利用可能 .Where("$.value.child.nodeType == 1") // ELEMENT_NODEだけを取得するためフィルタ .WriteLine(function(t) { return t.nestLevel + ':' + E.From(t.value.child.childNodes).First().nodeValue + ' . ' + E.From(t.value.parent.childNodes).First().nodeValue; });
例もパクりです。与えられた木から,子→親への対応を作る,を C# で - NyaRuRuの日記を、HTMLだったらツリーはDOMだよね、という感じで書いてみました。第二引数にはネストレベルを取得できるリザルトセレクターが使えます(省略も当然可能)。んー、DOMだとTextNodeが混ざるせいで、例として不適切に処理が混沌としてしまいました。あ、WriteLineの部分は子要素のFirstがTextNodeであると決め打ちしてます。正確にやりたいなら冒頭のinnerTextの例のようにWhereでTextNodeのみに絞った上で、ToStringで連結したほうが良いですね。
CascadeDepthFirstをCascadeBreadthFirstに変えると(両者は探索方式が違うだけで引数や戻り値は一緒)、こんな結果になります。
動作の差異がよく分かるんじゃないかなーと思います。
continue/break
ForEachでcontinue/breakを使えるようにしました。
E.Range(1, 100).ForEach(function(i) { if (i % 2 == 0) return true; // continue if (i == 7) return false; // break alert(i); // 1,3,5 });
灯台下暗しというか、こういうのって自分が必要になるまで気付かないというか。Linqって基本的に前段階のWhereで絞るからこの辺のものって必要になる機会が少ないんですよね。いや、言い訳ですけど。で、まあ、jQueryと同じでtrueをreturnすればcontinue、falseをreturnすればbreakになります。実装は超単純。
while (enumerator.MoveNext()) { if (action(enumerator.Current, index++) === false) break; }
ActionなのにFuncになってしまった!しまった!と、思わなくもないけど、この辺が後付けでグダグダになるのはしょうがないので気にしないことにしよふ。じゃなくて、基本はActionなのでヨシとしておこう。むしろこの辺はJavaScriptがユルフワなのでC#ルールに(用語を)当てはめようとして無理が出ているだけ。そういえばで、個人的にはreturn;でcontinue、return true;でbreakにしたいなあ、とか思ったり思わなかったりなのだけど、その辺は標準に合わせた方が混乱しなくていいよねー、と思うことにしました。
Suggest(モドき)
- JavaScript linq.js - 09.06/10
どうも、新PC組んだりして(Core i7だしメモリ12GだしIntelのSSD2枚刺しでこれで数年は買い換えないでも前線で闘っていける!と思いたいのだけど、そこら中で微妙にコスト削減で若干残念になっていたり、ExtremeじゃなくてMainstreamなところとか) まあ、それはそれ。で、Windows7 RC1を入れて若干の苦戦を強いられていて環境整えるのに時間かかりそう-、というわけでショボいネタを一つ。
// これが補完候補、ただの文字列配列 var list = ["tokyo", "kanagawa", "tochigi", "saitama", "sapporo", "nagano"]; // イベントハンドラは引数を二つ取る、一つは送り元、もう一つはイベント X.ID("inputBox").AttachEvent("keyup", function(sender, event) { var suggestArea = X.ID("linqsuggest"); suggestArea.RemoveNodes(); // 子ノード全削除 if (event.keyCode == 13) // eventはイベント { sender.value = ""; // 空にするだけ…… return; } var text = sender.value; // senderは送信元DOM要素、今回はTextBox // この4行が本編です、ええ suggestArea.Add(X.Elem("tbody", E.From(list) .Where(function(s) { return s.indexOf(text) != -1; }) .Select(function(s) { return X.Elem("tr", X.Elem("td", s)) }) )); });
入力補完モドきです。見にくいですけど、ソースコードの上にある入力ボックスにgaでkanagawaとnaganoが表示される、とかそんな感じ。エンターキーによる入力には対応してません、ただたんに絞り込んで表示するだけのデモです。んーと、suggestArea.Addからの、たった4行でリストの絞り込みして->DOM作成が出来るね、簡単だね、素晴らしいね、関数型構築とLINQの合わせ技は強力ですよね、だからみんなも使ってね、ということが言いたいそうです。世の中、WhereしてSelectするだけで9割方カバー出来る。気がする。気のせい。
ちゃんと真面目に作っていきたい気もするのですが、この強引リニアサーチのまま拡張しても野暮ったいだけで意味ないなー、と思うので気が乗らない。でも、大量のデータを扱うわけじゃなければ、この程度の手抜き実装でも普通に何とかなるものですよねー、とは思わなくもない。無理に洗練されたものを作ろうとせず、領域を見極めて労力を最小限にしたいと私は思ふ。いや、手抜きじゃなくて。手抜きですけど。
今後のlinq.xml.jsは微妙に機能足りてないというか、クロスブラウザ回りがグダグダなのと(Attributeにstyle指定がIEだとコケるとか!) DOMと混ぜ合わせてると気になるところが幾らかあるので、ボチボチと作っていきます。素のlinq.jsには深さ優先探索/幅優先探索を入れようとしているのですが、PC入れ替え中なのでもうしばらく後。
一体誰にアピールしてるの?な寂しさを感じつつも、それを言ったら前回の記事なんてどこからどうみても露骨なまでに受け狙いに走ったわりには大不発で寒すぎたので、それを考えたらマシです、いや、よくわからないけれど。でも簡単アピールは何か違う気がする……。うーん。まあ、誰が使わなくても自分は使うわけなので適当に行きましょふ。
最もタメになる「初心者用言語」はVisualStudio(言語?)
- C# JavaScript linq.js - 09.06/06
本題、に入る前にlinq.jsの更新内容を。今回からVisualStudio上でJavaScriptを書く際にIntelliSense(コード補完)のサポートが効くようになりました。これでもうリファレンスと睨めっこしなくてもすみます。IntelliSenseが出てくれば使えて、出てこなければ使えない。引数の型までは補完してくれませんが、メソッドチェーンに関しては100%信頼出来ます。利用するにはVisualStudio2008 SP1と対応パッチをあてる必要があります。私はProfessionalで確認していますが、Express Editionでも行けると思います。
もう一つ、linq.xml.jsのほうは、今回からXML/DOMの関数型構築が可能になりました(関数型構築の詳細は前回記事を参照ください)。それに伴い実例として、同梱してあるTwitterビューアサンプルも関数型構築に、ついでに@usernameにアンカーを張るように変更しました。
DOM構築時にLinqクエリを埋め込めると何が便利なの?のサンプル。全体像(obj.textって何だよ、とか)は同梱のものを見てください。obj.textは「@hoge hugahuga @foo hogehoge」な感じ(ようはTwitterの投稿の本文)で、文字列置換なら@usernameをa hrefで囲むだけだけど、DOMでやるにはどうしよう→全部マッチさせちゃえばいいぢゃない、それならSelectで射影すると楽ですね、という流れ。E.Matchesはキャプチャ付きの正規表現グローバルマッチが気楽に使えて非常に便利。
VisualStudioの薦め
以前に最もタメになる「初心者用言語」は、という話題で盛り上がっていたけれど、その時JavaScriptを挙げた人の多くが開発環境を入れる必要なく、テキストエディタがあればそれだけで始められるから、といったものが多かったけど、正直どうかなー、と思っていました。1年以上前のネタに今頃乗っかるのって感じですが今頃乗っかてみます。
初心者は、使えるメソッドが分からないから一々リファレンスを引くのに手間取るし(私は今だって文字列のsubstrとsubstringで悩む、特にJavaScriptはこの手の罠が多いし)、しょうもないタイプミス(大文字小文字とか)に悩まされてプログラムが動かない。演算子やカッコにスペースを空ければいいのかどうかも悩むし、一々手で直すのは手間だけど直さなければ汚いし……。
言語の学習って、特に最初はとにかく書くこと、それが最も覚えやすいと思うのです。リファレンスを引く時間も、手動でスペースを空ける時間も、言語の学習とは全く関係ない余計な時間です。限りなくゼロに近づけた方が良い。リファレンスを引く時間がゼロならば、その分だけ書く作業に時間を回せます。だから、テキストエディタじゃなくIDEを使おう。入力補完は一々リファレンスを引く必要もなく使えるメソッドをリストアップしてくれるし、ヘルプを見る必要もなくメソッドの解説が書かれている。補完があるからタイプミスも起こらないし、「長くてもいいから良い名前を付ける」習慣もつく。インデントだのスペースだのカッコの段落だのは、全てドキュメントフォーマッタ(VSではCtrl+E,D)で機械に任せるのが、完璧で綺麗で、何より手間がかからない。
そんなことより前に、そもそもforで変数がどう移り変わっているのかもよくわからないし。デバッガ、あったほうがいいでしょう、絶対に。あるのとないのとでは学習速度の桁が違ってくる。Firebugがあるって?いや、Firebugは確認用には物凄く便利だけど、それの上で開発とか辛くない?私は無理でした。ついでに言えば、Firebugインストールする手間もVisualStudioをインストールする手間も一緒。
Microsoft Visual StudioはWebインストールを選べば何の手間もいらずダウンロードされ、そのままインストールに入り30分程度で終わる(ダイアログの「はい」を押して待つすだけです、Eclipseと違って何入れればいいか、プラグインがどうのこうの、で悩む必要なくそのものがAll in One)、VSの使い方を知るのに30分。周囲に使える人がいれば、勘所を聞くだけで済むので10分。別に何も最初から全てを知る必要はなく、ダミーのASP.NETプロジェクト作って、F9でブレークポイント設定してF5でデバッグ実行してウォッチウィンドウで変数を監視する。それだけ分かれば十二分です(そこを分かるまでが辛い、というのはあります、分かっている人が横にいれば1分で済む話なのに、こんな初歩的な情報はあまりネットに転がってないから分かりようがない。需要がありそうなら書きますが……)
jQueryにも使える
jQuery大人気! やっぱjQueryが使えないとね!と、で、VisualStudioはjQueryに、jQueryオフィシャルが対応しています。どういうことか、というとjQueryのダウンロードページを見てください。Release NoetsとMinifiedとUncompressedと、Documentation: Visual Studio。そう、この公式に配布されているjquery-vsdoc.jsを使う(jquery.jsと同じディレクトリに置くだけです)と、VS上で丁寧に書かれた解説付きの入力補完が効くようになります。
jQueryの特徴といったらメソッドチェーン。ドット打つとの候補が出てくる出てくる、という感覚はとても楽しい。その楽しさを更に加速させるのがIntelliSense。メソッド名何だっけ、引数は何を取るんだっけ、と悩むこともない。ドット打って選ぶだけ。処理の本質部分だけに集中できる。
そういえばオブジェクトっぽい話が分かるかもしれないJavaScript講座が大人気ですけれど、便乗して言うとjQuery使うなら、まずVisual Studio、があれば、もうあとはドット打つだけなので何も考えないでもいいぐらい。んーと、本題はオブジェクトっぽい話、ですけれど、アレですね、何のかんのでオライリーのJavaScript 第5版を読むのが一番分かりやすかった。ケチッてWeb上のチュートリアルをブクマして読んで、なんてやるよりも、この場合は黙って金出して本読むのが時間の節約にも、得られる知識の濃さ的にもよろしいかと思われます。
amazonついでにLINQ本も張っておきます。LINQ本は以前に「LINQテクノロジ入門」という薄い本が出ているのですが、ほとんどLinq to Sqlのことしか書いていないので、少し高いのですが本で学ぶのならこちらの、プログラミングLINQのほうが良いです。初級-中級的にはLinq in Action
のほうがお薦めなのですが翻訳されてないしされる気配もなさそうですね、残念。そうそう、linq.jsも宜しくお願いします。
C#との高い互換性と、リファレンスにおいてあるLINQ Padはリアルタイムに動作を確認可能、なので学習用途ならかなりイイと思います。パフォーマンスは?実装がヘコい?是非とも突っ込みを!
入力補完の嬉しさ
まんじゅうの例を借りると、prototypeにメソッド突っ込んでオブジェクトほにゃららで何が嬉しいのか、というと、入力補完が効く。使えるメソッドがドット打つだけで出てくるし手入力する必要ない。jQueryのようにヘルプは出てきませんが、補完はちゃんと効くのです。同様に何のメソッドがあるのかさっぱり分からないDOM要素に対しても動くし、紛らわしさ全開な文字列に対しても効きます、便利便利。こうして眺めてると、知らないメソッドがあったりして勉強にもなる。
ところで突然C#の話に移って同様に、ポリモーフィズムで何が嬉しいの→入力補完が効く。ジェネリクスの何が嬉しいの→入力補完が効く。動的言語じゃなくて静型な型付け言語(C#とかJavaとか)の何が嬉しいの?→入力補完が超完璧に効く。型推論の何が嬉しいの?→入力補完が超完璧に効く上に更に型を入力する手間も最小限に抑えられる。
// これをそれぞれ二乗する int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // C# 2.0ではこんなクソみたいなコードが int[] c2 = Array.ConvertAll<int, int>(array, delegate(int i) { return i * i; }); // C# 3.0ではこんなに短く! Rubyっぽく見えませんか? var c3 = array.Select(i => i * i);
C#2.0だと、本質的な記述部分(i x i)以外の量の方が死ぬほど多くなってしまい、これじゃ嫌われて然りであり動的言語に行きたくもなる。何個intを書かせるんだよボケ、intなんて自明なんだよクソがって感じです。けれど、C#3.0からの型推論+ラムダ式があればとてもすっきりした上に、ちゃんと補完が効く(i x iを記述している最中もiがintであることを示してくれる)ので物凄く書きやすい。JavaScriptもいいけどC#も良いです。無名関数を愛するJavaScripterならC#のラムダ式はもっと愛せると思います(ウザいカッコとreturnが必要ないので)
C#は特にWindows用のデスクトップアプリケーションを作るなら、作りやすさで言えばベスト。.NET Frameworkは嫌だ?やっぱC++でしょって?うーん、ゲームを作るんでもないならC++の性能は必要ないし、作りやすさでいったらC#>超えられない壁>C++ですよ。C#は開発者に優しいゆるふわ言語。C++はF1マシン、とっても扱いにくいけど凄く速い。C#はプリウス。実用性と最先端ハイテクのバランスが絶妙。そもそも公道をF1カーで走ることが無茶苦茶なのです、公道はプリウスで十分どころか、むしろベストというもの。何にでも、適した領域というものがある。
しかも今C#を覚えると、もれなくSilverlightも付いてきます(Flashの開発言語がActionScriptであるように、Silverlightの主な開発言語はC#)。Flashに勝てるわけないだろヴァーカって?何にせよとりあえずツバだけでもつけておくと生きやすいかなーって思いません?Flash業界なんてレッドオーシャンもいいとこ、今から飛び込むならSilverlight。Microsoftは本気なのであっさりと消滅することはないでしょう。本気の時のMicrosoftはとってもしぶとくしつこい。
でもまあ、そんな云々はどうでもよくて、普通にLINQ楽しいですよ!が一番に言いたいことです。ええ、LINQ楽しい。LINQがあるからC#万歳だし、LINQがなくても作ったのでJavaScriptも万歳。linq.jsは初回リリース時にチュートリアルめいたものが書いてあるので、よければどうぞ、というかお願いしますお願いします。
DOMの関数型構築
- JavaScript linq.js - 09.05/29
Linq to XMLのJavaScript移植、もポチポチと進めています。
var xml = new XElement("customer", new XAttribute("id", "c001"), new XElement("firstName", "Paolo"), new XElement("lastName", "Pialorsi"), new XElement("addresses", new XElement("address", new XAttribute("type", "email"), "paolo@devleap.it"), new XElement("address", new XAttribute("type", "url"), "http://www.devleap.it/"), new XElement("address", new XAttribute("type", "home"), "Brescia - Italy") ) ); alert(xml.Source.outerHTML); // .Sourceは気にしないでください(最終的には隠蔽されるので)
これは何と、C#にコピペしても動く。つまり互換性100%(笑) 所謂マークアップビルダになります。私がC#っ子だから、というのもあるけれど、JSON風に書くより好き。JSON風だとそれぞれのライブラリが属性対応のため変則的な書き方ルールが用いられていて、それを覚えなきゃいけないけれど、これなら要素に属性と値を突っ込むだけなので学習コストがほとんどない。と、思う。IntelliSenseのサポートも効くし(現在linq.jsをIntelliSenseが効くよう整備中です、土日に書きすすめて公開、したいなあ)。そのかわりnewだらけでちょっと冗長に過ぎるところはあるかもしれない。
// ユーザー名と発言と投稿を持ったクラス var Twit = function(user, text) { this.user = user; this.text = text; } // とりあえずサンプルデータ var data1 = new Twit("ika", "いかはいかが"); var data2 = new Twit("tako", "たこやき"); var data3 = new Twit("sakana", "丸焼き"); // XmlHttpほげほげだのJSONだのな結果の例として配列 var list = [data1, data2, data3]; // リストをSelectでXElementに射影すると自動的に展開される var xml = new XElement("twitter", new XElement("account", "neuecc"), new XElement("twits", E.From(list).Select(function(t) { return new XElement("twit", new XAttribute("user", t.user), t.text) })) ); alert(xml.Source.outerHTML); // .Sourceは気にしないでください(最終的には隠蔽されるので)
例をもう一つ。例が適当かつ分かり辛くてすみませんなのですが、Selectで射影しておくと、展開されます。何らかのオブジェクトを繰り返してDOMにする、というシーンは多いと思うのですが、forはもう必要ありません。むしろfor禁止。ループは悪。「この配列を」「この形に展開する」と宣言的に記述するだけで良いのです。
var table = new XElement("table", new XElement("tr", new XElement("td", "column1"), new XElement("td", "column2"), new XElement("td", "column3") ), E.Repeat("tr", 5).Select(function(tagName) { return new XElement(tagName, E.Range(1, 3).Select(function(i) { return new XElement("td", new XAttribute("style", (i % 2 == 0) ? "background:red" : "background:green"), "row" + i); }) ) }) ); // FirefoxではouterHTML動きませんけど、最終的には隠蔽されるので(ry document.body.innerHTML = table.Source.outerHTML;
簡単なTableの生成例。これrowになってませんね、あはは。まあ、いいや。関数の引数の中に式を書くという感覚が最初はキモかったのですが、今では平然と書くようになってしまいました。何というLinq中毒……。しかし、JavaScriptだとfunction(){return }のせいでゴチャゴチャして分かり難いものになってしまう。ラムダ式欲しいなあ。{とかreturnとかいらない。Firefox3では省略出来るけど、Chromeはまだ出来ないみたい……。
Firefox3は使えば使うほど嫌いになるという素敵ブラウザなので、本気でChromeに乗り換える気はありますよ!アドオンがなければ自分で作ればいいじゃない、と思うことにしますので、作りたいですね。一応Hello,Worldは書いた。
<script type="text/javascript" src="linq.js"></script> <script type="text/javascript" src="linq.xml.js"></script> <script type="text/javascript"> // Initialize X.Initialize(function() { X.ID("panel").AttachEvent("click", panel_onclick); }); // EventHandler function panel_onclick(sender, event) { X.ID("titleLabel").SetValue(event.screenX); } </script> <div id="panel" class="toolstrip-button"> <span id="titleLabel">Hello, World!</span> </div>
ええ、ただのHTMLです。ステータスバーのところにHello,Worldが出てクリックできるという、それだけのもの。Chromeの拡張はまだ何が出来るか分からないというか何も出来ないというか、それグリモンでいいよね、的なことしか出来ない感じなので、その間、今のうちにlinq.xml.jsを完成させよう。何かもう意地でも自分は使う、な方向に傾いてる気がしますが。jQuery?何それ。