Archive - Software
XboxInfoTwit - ver.2.4.0.2
- XboxInfoTwit - 12.01/16
Xbox.comがリニューアルしたので、それに対応しました。今回は3日ほど本気で気づいていなくて対応が遅れてごめんなさい。最近はコメントやリクエストも放置気味で、大変反省しています。転職して少し忙しくなって、あまり気が回らなくて、という言い訳カッコワルイ。もう少し頑張ります。例によって全くテストしてないので、こいつ放置気味だしどうせ反応してくれないしいいかー、とか思わず、どうか変なところあったら報告お願いいたします。
あ、あと、今回からエラー時に前回の状態をリセットしないように変更しました。どういうことかというと、何らかのエラー(Xbox.comが不調だったり←よくある、Twitterが不調だったり)によって状態がリセットされた結果として、Power Onの投稿が連投されたりしてナンジャコリャー、といったような状態になることが防げます。多分。恐らく。きっと。
XboxInfoTwit - ver.2.4.0.0
- XboxInfoTwit - 11.11/13
Xbox.comがリニューアルしたので、それに対応しました。今回より.NET Framework 4.0専用になりましたので(今までは3.5)、もし動かなくなった!とかの場合は、.NET Framework 4.0をインストールしてください。
最近はすっかり放置気味ですみませんでした、わざわざブログのコメント欄に報告頂いたものもスルーしていて、大変申し訳ありません。ええと、一応、今回プログラムを少し見直しまして、最近絶不調にエラーばっかだったと思うのですが、若干改善されたのではかと思います。
あと、リニューアルにともない、内部がかなり変わったんですが「全然テストしてない」ので、動作がヘンテコな可能性は大いにあります。変なとこあったら報告していただけると助かります。
linq.js LT資料
- JavaScript linq.js - 11.08/22
LTで簡単にlinq.jsの紹介をしましたので、その資料を。といっても、資料は全く使わないでLTの場では完全にデモ一本にしました。ええ、こういう場では、やっぱデモ優先のほうがいいかなー、と。資料は資料で、要素がきっちりまとまって紹介という感じなので、見てもらえればと思います。
スライドのテンプレは同じの使っていてそろそろ飽きたので、新しいのに変えたいところ。基本的にはテンプレのテーマまんまですが、やっぱ細かいところでスライドマスタの調整は必要なので、面倒くさー、と思ってしまい中々に気力が。むしろデザイン変更は一年に一回でいいかしらいいかしら?
そういえばどうでもよくないのですが、SlideshareをBlogに埋め込む時はlargeサイズを選んで欲しい。文字潰れてしまうもの、わざわざ小さいサイズで埋め込む必要はどこにもなくて。
文字列を先頭から見て同じところまで除去をlinq.jsとC#で解いてみた
- C# JavaScript Rx linq.js - 11.08/19
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入門記事を書きました
- JavaScript linq.js - 11.06/09
お話をいただき、@ITの.NET TIPSにlinq.jsの入門記事を、二週に渡り書きました。
- JavaScriptで配列をLINQにより処理できるライブラリ「linq.js」を利用するには? - @IT
- jQueryと「linq.js」を連携させてDOMをLINQにより処理するには? - @IT
このサイトでやると、アレもコレもとダラダラと書いてしまって分かりづらくなっていたのですが、記事では文字数制限などのお陰で構成がすっきり、校正してもらったお陰で文章の揺れもなく。つまるところ、ほとんど編集で助けてもらったというだけで、本当にありがとうございました。サンプルコードは、コードを見ただけで伝わるよう単純に、でもlinq.jsの威力を伝えなければならないので多少の複雑さは持たなければならない。などと思い結構悩んで作りました。
お陰様で反響も結構良かったみたいでなによりです。実績も良く分からない外部ライブラリは導入できない…… という方も、@ITに載ってるから大丈夫だよ!を説得材料(?)にできるのではないでしょうか。これを機に、是非試してみてください。
ところで幾つかの話。
メソッド名大文字
失敗した。かな……。特にTojQuery()でjQueryと見かけ上(あくまで見かけだけなんですが)シームレスに繋がっていると違和感が結構あります。以前に、jQueryとLINQの世界が視覚上切れて見えるから、むしろイイぐらいなんです、とか言ってましたが勿論ただの強がりです。この辺は今更変えにくいところで、どうしたものかな、と悩んでいるところです。
パフォーマンスについて
気になりますよね?ベンチマーク的に言えば、遅い。遅延評価の実現や豊富な機能は、速度を相当犠牲にしています。では、その遅さが許容できるほどか無視できないか。これは、卑怯な逃げ口になってしまいますが、状況次第。少なくとも普通のサイトでは問題ないレベルだと思いますし、また、ブラウザのJSはどんどん速くなっていってます。Chrome/Fx4/IE9の速さ!「許容できる」の範囲はどんどん広がっていってるのでは、と。
こういった話はC#でもそうです。LINQはベタforループより確実に遅い、が、じゃあベタforループで書くかといったら、よほどエクストリームに速度を求める場合以外は書きはしません。
Pentium3でWindows XPでIE6な奴らにも配慮する!というのも確かに美学なのですが、そうではない方向も見ないと、素敵な未来はやってこないのではないかな、って。21世紀にもなるのにループアンローリングで高速化!とかいう記事ばかりが踊る世界なんて悲しいじゃないですか。
モバイル機器のことも考えなければならないし、HTML5も控え、JavaScriptで高負荷な処理をすることも少なくないので、まだまだ時代は追いついていないけれど。それでも、私はもう少し先の未来を見ていたい……。JavaScriptがこれから先、本当に一級の言語となっていくのなら尚の事です。
XboxInfoTwit - ver.2.3.0.4
- XboxInfoTwit - 11.04/29
Twitterの認証周りが変わって、新規認証が出来なくなってしまってたので、それを修正しました。
XboxInfoTwit - ver.2.3.0.3
- XboxInfoTwit - 11.02/24
Xbox.comの認証周りが変わってログイン出来なくなってしまってたのですが、それを修正しました。朝っぱらに急ぎで対応したので、あまりテストしてません。マズいところあったら後で直します。
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を生成している部分に直書きで挟んでやればヨシ。スクリプトの軽快さは良いですね。なので設定というか読み込むファイルも先頭のほうで普通に直書きで指定しちゃっております。
そのまま使ってもいいんですが、ビルド後に実行するコマンドラインに指定してやれば一切の手間暇なく常にフレッシュ。おお、素敵。
XboxInfoTwit - ver.2.3.0.2
- XboxInfoTwit - 11.01/21
電源OFFなのにONということになって投稿し続けるという酷い暴走していました、ごめんなさい……。というわけで直しました。
解説(言い訳とも言う)
XboxInfoTwitはスクレイピングという、普通にブラウザで表示する際のページのHTMLを解析してデータを取り出しています。今回は、HTML側でほんのちょびっと変更があって、その影響をモロに被って暴走しました。Ouch!こういう、うまくデータが取れなかった場合を想定してちゃんとエラーにしないとダメなのですね。私は今回はその辺まったく対策取ってなかったので、……といった結果になってしまいました。ほんとすみません。
一応、次からは今回のようなのが原因の暴走はなくなりました。別の原因がもとに、ってのは、うむむ……。
Linqと総当り
- C# JavaScript linq.js - 10.12/28
各所で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 Rx linq.js - 10.12/20
このエントリは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とは何ぞや、統合言語クエリがうんちゃらかんちゃら、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 }) });
コード似てませんか?今まで出してきた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); });
胡散臭い(笑)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にタッチ!
XboxInfoTwit - ver.2.3.0.1
- XboxInfoTwit - 10.10/21
Xbox.comがリニューアルされました!というわけで新Xbox.comに対応しました。それだけです!そして、Xbox.comの構造が変わったお陰で取得が軽量化されました(情報取得のための巡回ページ数が大幅削減されたため)。ヨカッタヨカッタ。あまり良い評判を聞かない新Xbox.comですが、こういうところで良くなってますよー、と。地味な変化としては、今回からフレンドが0な場合でも取得/投稿できるようになりました。たまにフレンドいないから使えない!という嘆きを見かけるので、ちゃんと使えるようになってヨカッタヨカッタ。
例によってテストが非常に不十分なため、ゲームタイトルによっては投稿できなかったりするかもしれません。もし怪しいところがあったら、Twitterで投稿文に「XboxInfoTwit」を含めてポスト、例えば「GoW2がXboxInfoTwitで動かないんだけど何これ」とか言ってもらえれば、検索経由で発見しますので気楽に文句書いてください。(GoW2は動きますけど)
XboxInfoTwit - ver.2.2.0.4
- XboxInfoTwit - 10.10/14
未知のエラーが出まくっていたので、暫定というか適当な対処を取ってみました。コードがかなり古くて汚いので、根本的に手を入れたいところなんですが、作業量を考えると中々やる気が沸かないという微妙な状態。機能追加のリクエストも数点頂いているので申し訳ないんですけどね。
TwitterTLtoHTML ver.0.2.0.0
- TwitterToHTML - 10.09/02
TwitterのBasic認証の有効期限が8月いっぱいだったのですよね。それは知っていて動かなくなるのも知っておきながら、実際に動かなくなるまで放置していたという酷い有様。結果的に自分で困ってました(普通に今も使っていたので)。というわけで、TwitterTL to HTMLをOAuth対応にしました。認証時のアプリケーション名がTL to HTMLなのですが、これは「Twitter」という単語名をアプリケーション名に入れられないためです。面倒くさいねえ。
OAuth認証は@ugaya40さんのOAuthAccessを利用しています。XboxInfoTwitでは自前のものを使っていたのですが、どうしょうもなく酷いので自分で作り直すかライブラリを使うか、でズルズル悩んで締切りを迎えたのでライブラリ利用に決定ー。使いやすくて良いと思います。
