Archive - Software
ltxml.js - LINQ to XML for JavaScript
- JavaScript linq.js - 12.10/24
以前、linq.js ver.3のセッションの時に、ちょびっとだけ触れたLINQ to XML for JavaScriptが公開されました!
作者は私ではなく、Eric White氏です。誰?ってことですが、元Microsoftの人です。氏のMS時代のMSDNブログのLINQ to XMLタグには超DEEPなLINQ to XMLの使いこなしが書いてあって必見。というわけで、非常にLINQ to XMLに詳しい、というか、MSDNのLINQ to XMLの解説ドキュメントを書いたのは氏だそうです。なので、詳しいとかそういう次元じゃなく、普通に中の人ということですね。
概要
そんなわけで、ltxml.jsとは、C#のXML操作ライブラリであるLINQ to XMLのJavaScript移植です。C#のLINQ to XMLがLINQ to Objectsの上に構築されている(ElementsやDescendantsの戻り値がIEnumerableとなり、LINQベースのクエリ操作となる)ように、ltxml.jsはLINQ to ObjectsのJavaScript移植であるlinq.jsの上に構築されています。ltxml.jsのelementsやdescendantsは、linq.jsのwhereやselectなどのクエリ操作によってXMLを展開できます。
C#版と構造はほとんど一緒です。ただし、JavaScriptの慣習に則りメソッド名がlowerCamelCaseであることと、プロパティが.getHoge()であること(ただしECMAScript 5に対応しているならば.hogeといったようにプロパティでアクセスできます)、オペレーターオーバーロードが存在しないことによる挙動の違い程度です。また、C#版よりも機能向上している面もあります。それは、私がlinq.jsにおいてC#のLINQ to Objectsで物足りないと思った機能を追加していたようなもの、でしょうか、多分ね、きっと。
また、パフォーマンス上の考慮により、descendantsなどは、デフォルトは即時実行で配列(をEnumerableでラップしたもの)を返します。.lazy = trueをXElementなどに投下することで、完全な遅延実行になります。もし巨大なXMLを扱うのならば、遅延実行が効果を発揮するでしょう。通常考えられるサイズのXMLならば、デフォルトのとおり即時実行のほうが良好だと思われます。
使い方
ぶっきらぼうにも、ドキュメントがほとんどないですね!まあ、それは追々紹介されていくことでしょう。ともあれ現状は、ファイルをダウンロードするとltxmlTest.htmlというファイルがあって、それがユニットテスト兼サンプルになっているので、とりあえずそれを読みましょう。また、JavaScript特有の違いはあるものの、基本的にはC#のそれと等しいので、MSDNのLINQ to XMLの解説ドキュメントがまんま使えないこともないです。
ともあれ、まずは簡単なXMLをパースしてみましょう。
var xml = "<Contacts>\ <Contact>\ <Name>Peter Hage</Name>\ <Phone>206-555-0144</Phone>\ </Contact>\ <Contact>\ <Name>John Hoge</Name>\ <Phone>106-333-2222</Phone>\ </Contact>\ </Contacts>"; // parseでただの文字列からLINQ to XMLのXElementに変換 var xElem = Ltxml.XElement.parse(xml); // 子孫ノードのNameを選択し、値だけ抽出 var names = xElem.descendants("Name") .select(function (x) { return x.getValue(); }) .toArray(); alert(names); // Peter Hage, John Hoge
descendants.selectといったように、LINQです!完全に!これをLINQと言わずして何をLINQと言うか!
名前空間
ltxml.jsの全てのクラスはLtxmlオブジェクトの下に格納されています。グローバルを汚さない。しかし、いちいちLtxml.XElementなどと呼ぶのは面倒くさい話です。以下のようなショートカットを先頭に用意するのをお薦めします。
var XAttribute = Ltxml.XAttribute; var XCData = Ltxml.XCData; var XComment = Ltxml.XComment; var XContainer = Ltxml.XContainer; var XDeclaration = Ltxml.XDeclaration; var XDocument = Ltxml.XDocument; var XElement = Ltxml.XElement; var XName = Ltxml.XName; var XNamespace = Ltxml.XNamespace; var XNode = Ltxml.XNode; var XObject = Ltxml.XObject; var XProcessingInstruction = Ltxml.XProcessingInstruction; var XText = Ltxml.XText; var XEntity = Ltxml.XEntity; var XEnumerable = Ltxml.XEnumerable;
また、C#版ではEnumerableへの拡張メソッドとして用意されていた幾つかのメソッドは、ltxml.jsではEnumerableに追加されているasXEnumerableを呼び、XEnumerableへと変換することで、呼び出すことができます。しかし、もしそれを手間だと思う場合は、linq.jsのEnumerableを拡張することで、よりスムーズに接続することが可能です。ただし、C#版ではジェネリックによって区別されていましたが、JavaScriptではジェネリックが存在しないので、汎用性のないシーケンスの要素がltxml.jsに固有でなければならないメソッドをEnumerableに追加することとなります。また、removeなどは、他の人の拡張と名前が衝突する可能性が高いことなどにも注意。
Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements; Enumerable.prototype.ancestors = Ltxml.XEnumerable.prototype.ancestors; Enumerable.prototype.ancestorsAndSelf = Ltxml.XEnumerable.prototype.ancestorsAndSelf; Enumerable.prototype.attributes = Ltxml.XEnumerable.prototype.attributes; Enumerable.prototype.descendantNodes = Ltxml.XEnumerable.prototype.descendantNodes; Enumerable.prototype.descendantNodesAndSelf = Ltxml.XEnumerable.prototype.descendantNodesAndSelf; Enumerable.prototype.descendants = Ltxml.XEnumerable.prototype.descendants; Enumerable.prototype.descendantsAndSelf = Ltxml.XEnumerable.prototype.descendantsAndSelf; Enumerable.prototype.elements = Ltxml.XEnumerable.prototype.elements; Enumerable.prototype.nodes = Ltxml.XEnumerable.prototype.nodes; Enumerable.prototype.remove = Ltxml.XEnumerable.prototype.remove;
私個人としては、Enumerableへの拡張はそんなに薦められないかな、という感じですが、ヘヴィにXMLを処理する局面では、拡張したほうがサクサク書けて良いのではかとも思います。この辺は好みでどうぞ。
関数型構築
XMLを作るときは、コンストラクタで可変長引数として連鎖させます。これをLINQ to XMLでは関数型構築と呼んでいます。
var xml = new XElement("root", new XElement("user", new XAttribute("id", 1), new XElement("age", 100))); // <root><user id = '1'><age>100</age></user></root> alert(xml.toString()); // toStringで文字列化
閉じタグが不要であったり、安全であったり(JavaScriptだってカッコ閉じ忘れとかは警告入るからね)と、生文字列で組み立てるのに比べて、遥かに利点があります。また、要素がlinq.jsのEnumerableである場合も、きちんと展開されます。
// C#と同様にEnumerable<XElement>は展開される var users = Enumerable.range(1, 10) .select(function (x) { return new XElement("user", new XAttribute("id", x), new XElement("age", x * x)); }); var xml = new XElement("root", users); // <root> // <user id = '1'> // <age>1</age> // </user> // <user id = '2'> // <age>4</age> // </user> // (略) // </root> alert(xml.toString(true)); // 引数にtrueを渡すとインデントつけて整形
どうでしょう、イメージつきます?
用途
Eric White氏がOpenXML(Officeのxlsxとかがそれ)の専門家ということで、JavaScript上でOfficeファイルを容易に扱うことが可能になるでしょう。つまり、サーバーサイドでのコンバート処理など不要に、JavaScriptだけでビューワーであたり要素抽出であったりが、完結する未来があります。なんて興奮する話でしょう!
とはいえ、それはあまりにも専門的すぎて、実に面白いし役立つでしょうけれど、実際にそれでもって作り上げる側に周るのは極少数の人に違いありません。では、他にXMLを使う局面なんてあるのか?ふむ……。恐らく、ブラウザ上で動くアプリケーションにとって機会はないでしょう、どこもかしこもJSONに集約される!AJAXのXはXMLのX!だった時もありました。いや、今もそうでしょうけれど。
では、どこに?というと、Node.jsはどうだろう?結局、未だにXMLのサービスなどは多いのだ。RSSはJSONにならないでしょう。サーバーサイドで行うならXMLは無視できないのだ。またはクライアントアプリでも、TitaniumやPhoneGapはどうだ?またはWindows 8のアプリケーションはJavaScriptで作ることができる。そこではまだまだXMLは現役に違いない。JavaScriptの活躍範囲がブラウザを超えていけばいくほど、残り続けるXMLに遭遇する機会は増える。
AtomPub(ああ!今はもうあまり名前を聞かない!)の構築に、LINQ to XMLの関数型構築は役に立つことでしょう。とにかく言えることは、XMLは決して死んでいないし、望まれるか望まれないかはともかくとして、生き残り続けるでしょう。そのために、私達には強力な武器が必要なのです、LINQ to XMLという。
もしくは、単純にHTMLビルダーとして使ったっていいかもしれない。HTMLはXMLなのだもの、ええ、大丈夫、そのようにも使えます。文字列連結してHTMLを組み立てるなんてしなくていい。また、もちろん、HTMLのDOM操作にだって、ね(でもDOMの操作ならば、きっとjQuery使いますね)
未来
ところでしかし現状ltxml.jsはベータです。何故か。linq.jsがまだベータだからです。ver.3.0.3-Beta4ってなんだよクソが。すみませんすみません、なるべく早く正式リリース出来るよう鋭意努力します。NEETなので暇、じゃあないんですよねえ、残念ながら。でも急ぎます。さすがに!いやほんと私の作業ペースの遅さには我ながらどうかと思う感じですが、もうさすがに猶予ないですね!
ちなみに7月31日に作ってるよー、ってメール貰って、そこから何度かやり取りしてました。ltxml.jsのコード自体、かなりパワフルにlinq.jsを使いこなしているので(私のアドバイスの賜物です!)そういう意味でも面白いですね。ちなみに、その時には8月中にlinq.jsリリースするって言ってたかなあ、今もう10月末ですねえ、どうなってるんでしょうねえ、ごめんなさいごめんなさい。
linq.jsのTypeScript対応とTypeScript雑感
- JavaScript TypeScript linq.js - 12.10/12
MicrosoftからTypeScriptという新言語が発表されました。驚くべきは、あのC#のAnders Hejlsbergが関わっている!これはもう触るしかない。そしてこれはコンパイル後にJavaScriptになる言語(CoffeeとかJSXとかみたいな)なわけで、じゃあlinq.jsを対応させるしかない!というわけで、させました。
// TypeScript Enumerable.range(1, 10) .where(x => x % 2 == 0) .select(x => x * x) .writeLine(); // コンパイル後 Enumerable.range(1, 10).where(function (x) { return x % 2 == 0; }).select(function (x) { return x * x; }).writeLine();
ひゃっはー、もうfunction() { return }とはオサラバだ!そしてこの記述性と最強のコレクション操作であるLINQが合わさると最強に見える。
に型定義ファイルは同梱してありますので、是非是非お試しを。NuGetのlinq.js -Preでも入ります。NPMは予定はありますが、まだです、すみません。
TypeScriptについて
型安全なCoffee Script、といった印象ですね。基本的にはCoffee Scriptに近いと思います。JavaScriptにプラスアルファな構文を採用することで、既存のJSライブラリとの繋がりを良くすることと、綺麗なJavaScriptを吐くことに重きが置かれている。TypeScriptは、比較的素直にJavaScriptに読み解くことが出来て、独自のコード生成は、現状はほぼほぼ無いし、意図的に省かれているようです(例えば非同期にたいしてasync構文を入れたりすると、大量のコード生成が入り、出力されるJavaScriptが機械的に汚れがち)。
そういった点、機能面では、TypeScriptには物足りなさを感じるところが多いかもしれません。じゃあJavaScriptに対する強みってどこなんだよ!といったら、一つはJavaScriptの冗長な記述性の補正(class,module, arrow function, Object.create/definePropertyとかも最低だしね)。もう一つは、無理なく自然に馴染んだ型付け。
型はないよりあったほうがいい。でも、型を付けるのがあまりにも苦痛だったら?ちょっとしたコードを書くのにも型!型!型!と押し付けられたら?そりゃあ、嫌だ。というわけで、型推論によって、比較的スムースに書けるようになっています。
型推論の性質というか範囲というか強さというかは、C#と非常に近いというかC#とまるで一緒なので、C#erならサクッと馴染めます。もっと強力な型推論のある言語に馴染んでいる人だと、え、ここで効かないの?みたいな違和感はあるかもですが。
また、さすがはMicrosoftというかAnders Hejlsbergというか、入力補完のことを念頭に置いた言語設計になっているので、IDEとの相性が非常に良い。そして最初からVisual StudioによるIDE環境が用意されていることで、型のある利点の一つであるリアルタイムエラー通知や入力補完をたっぷり満喫できます。さらに、それはTypeScript PlaygroundによってWeb上でも体感できます。というか、もはやPlaygroundはWeb IDEとでも言うべき驚異的な動き!
また、Windowsだけではなく、最初からSublime, Vim, Emacsの対応ファイルが用意されているというところからも、Windowsに限らず幅広く請求したい、という表れだと思います。そして実際、言語はプラットフォーム中立なわけです(最終的にはJavaScriptだしね!)。
Structural Subtyping
TypeScriptの最も面白いところは、ここです。C#とかのインターフェイスとLLのダックタイピングの中間、みたいな。実にゆるふわなJavaScriptと絶妙に合っててイイ!というかそもそも私はStructural Subtypingって名前だけでぜーんぜん分かってなかったのですが、TypeScriptだと自然と馴染めます。ほむ、どういうことか、というと、一例を。
union(second: any[], compareSelector?: (element: any) => any): Enumerable; union(second: Enumerable, compareSelector?: (element: any) => any): Enumerable; union(second: { length: number;[index: number]: any; }, compareSelector?: (element: any) => any): Enumerable;
これはlinq.jsの型定義の一つでunion、和集合を生成するためのメソッドです。なので、元シーケンスと、対象シーケンス(second)が対象になるわけですが、じゃあシーケンスって何?と。列挙できればいいので、配列もそうだし、Enumerableもそう。そして、JavaScriptに特有の存在として、配列みたいだけど配列じゃあないもの(lengthを持っていてインデクサでアクセスできる)、例えばDOMのNodeListとか、もそう。
で、そういった「lengthを持っていてインデクサでアクセスできる」という型の定義が{ length: number;[index: number]: any; }。これにより、DOMのNodeListやjQuery([0]とかでHTMLElementが取れる)など、配列みたいだけど配列じゃないもの全てが型安全に定義されました。やったね!
もしC#だったら、対象はインターフェイスで指定するしかないので、IEnumerable<T>を実装していないクソコレクションクラスが存在したら、それは列挙不能になってしまいます。片っ端からオーバーロードを作るのは不可能だし、かといってdynamic secondなどとしてしまってはアレ。
とはいえ、基本的にC#では最初から全てのシーケンスはIEnumerable<T>を実装している、という前提が成り立っているので、問題はおこらない。でも、JavaScriptは違う。配列みたいだけど配列じゃあないもの、が跋扈してる。でも、そこをanyとして何でも受け入れられるようにしたら型安全じゃあない。安全にしたい。そこをStructural Subtypingが華麗に解決してくれました!惚れた……。
TypeScriptはJavaScriptか?
JavaScriptコードはそのままTypeScriptだ!ということにYesと言えるかというと、イエスでもあり、しかし割とノーです。私がこの話を聞いて、最初に思ったのは、既存JSコード、つまりライブラリ類もそのままで動くのかな?と。答えはNOです。JS固有の、実行時に切った貼ったして構造作っていくの、ああいうのTypeScriptだと軒並みコンパイルエラーになるので、ダメです。ほとんどのライブラリが絶滅でしょう。
と、勘違いしていたのですが(yharaさん指摘ありがとうございます!)
declare var $; // jQuery declare var _; // underscore declare var Enumerable; // linq.js
とかって定義すると、これはそれぞれ $:any, _:any, Enumerable:any という扱いになって、以降はどんなチェーンを繋げてもエラーが起こらない、つまりライブラリが正常に読み込めたかのようになります。
ただ、型チェックや入力補完が効かなくなるので、TypeScript用の型注釈ファイルはあったほうがいいですね。有名ライブラリはともあれ、無名プラグインとかは自前で型注釈書かなければならないかもり。手書きだとかったるいので、自動生成である程度テンプレート吐き出してくれないと、面倒くさい。この辺はMicrosoftだしやってくれるんじゃないかなあ、という淡い期待を抱いていますが……。
とはいえ、ちょっとしたコンパクトなプラグインを使ったり、ライブラリ使うとしても一部分だけだしー、などというのに、わざわざ型定義も馬鹿らしいわけで、さくっと動的な感じにdeclareできちゃう、というのは素晴らしい話。
そんなわけで、JavaScript→TypeScriptの相互運用性としては、繋がりはかなり良好。勿論、jQueryなどもスムースに扱うことができます。これは、文法がJavaScriptプラスアルファで構築されているがことの利点です。そしてTypeScript→JavaScriptは、というと、素直なJavaScriptを吐いてくれることもあり、良好です。TypeScriptで作られた生成物は、TypeScriptだけに閉じません。
JavaScriptを中間言語とする選択肢が増えた。JavaScriptを介することで他の言語とも自由に繋がる。ここには、Webの互換性、中立性を崩す要素は一切ありません。独自言語による囲い込みとかではありません。素直に歓迎できるはずです。ただし、言語としてはあくまでTypeScriptはTypeScriptです。そこだけは、誤解しないほうがいいと思います。文法的に、ES6を若干取り入れているとはいえ、違う言語です。将来的にもTypeScriptはEcmaScriptにならないでしょうし、EcmaScriptはTypeScriptにはならないでしょう。TypeScriptはEcmaScript6のただの代替なのではない、別の価値ある言語です。
変な期待をして、これJavaScriptじゃないじゃん、とかって難癖つけたりは、あまり良くないですね。
TypeScriptとVisual Studio
別にMicrosoft環境に閉じる言語ではないので、EmacsでもVimでもいいですが、やはりVisual Studioが第一な点は少なからずあります。LinuxでもIDEで書きたい?きっとJetBrainsがWebStormに搭載してくれるはずです!(実際、Voteはかなり集まってました)
ともあれ、Visual Studioです。専用拡張のインストールはTypeScriptのサイトから出来ます。プロジェクトテンプレートが何故かC#のところにあって気づきにくいことに注意!それともう一つ、Web Essentialsを入れましょう。元々Coffee ScriptとLESSに対応していたのですが、今回TypeScriptにも対応してくれました。Web Essentialsを入れることで、保存時のコンパイル(通常の拡張だとビルド時のみ)と、ウィンドウ分割での出力後のJS表示、それとSourceMapファイルの出力を行ってくれます。
勿論、IntelliSenseはフルに効くしエラーはリアルタイムでがんがん通知してくれます。TypeScript Playgroundと違うのは、エラーがあるとJSに変換してくれないところですね。まあ、それは正しい挙動なのでいいです。Playgroundで中途半端なエラーのある状態でもガンガン変更表示してくれるのは、それはそれで便利なので、それもまたいいです。
ちなみに、TypeScript Playgroundでは赤波線が出ている状態は、一応、JSを出力してくれてますが、それはコンパイルエラーの状態で完全な出力がされていないと思って良いです。つまり、本来的には動いてないわけです。この動いていない出力を指して、(現状Firefoxにしか乗ってない)JavaScriptへの互換が不完全とかって難癖つけたりするのは、ほんと良くないですね……。
SourceMap
Web Essentialsの吐いてくれるSourceMapとは何ぞや、というと、これはTypeScriptのままデバッグができます。コンパイル時にJSを吐いてくれる系言語の欠点として、デバッガを使ったデバッグが困難、というのが挙げられますがSourceMapを使うとそれも解決、します。
現状、対応ブラウザはChromeと、まあ、他は知らないのですが、とりあえずChromeは対応しています。IE10(とVS2012内蔵デバッガ)も対応してくれると嬉しいなあ。Chromeのデバッガの不満点としては、ブレークポイントが行単位でしか貼れないことですね。ラムダ式の内側に貼れないと、特にLINQのような一行ラムダを多用するものではデバッグがとても不便でして。この辺、改善されていってくれると嬉しい話。
vs JavaScript(のIntelliSense)
実は、VisualStudio 2012のJavaScriptはかなりサポートが手厚く、裏で常にコードを実行して補完候補を出してくれたりします。
なので、純粋なIntelliSenseの効きだけでいうと、TypeScriptはJavaScriptに負けているかもしれない!如何せん、特にlinq.jsではシーケンスの要素がanyになってしまいますからね。JavaScript(を裏で動かして解釈する)ならば、ここも補完効いてしまうという。最近のJavaScript IDEは進化しすぎで恐ろしい……。
ジェネリクス
仕様書にも明言されていますが、正式リリースまでには搭載する予定があるそうです(ちなみに現在は0.8)。ジェネリクスが乗っかるとlinq.jsがすっごくパワフルになるんですよ。如何せん、今はシーケンスの要素の型が全てany扱いで補完が全く効かなくてTypeSafeでもなんでもないのですが、ここが型付けされると完璧なIntelliSense生活!C#並というかむしろC#超えるぐらいの勢いでパーフェクトなLINQ to Objects!なので、相当に待ち遠しいです。
Compiler as a Serviceの未来
TypeScriptのコンパイラはTypeScriptで書かれてます。これ、別にかっこつけとかでもなんでもなく、非常に重要な意味を持ちます。で、いきなり分かりやすく成果物として出してくれているのがTypeScript Playground。構文解析がJavaScriptで可能だから、Web上で全て完結するIDEが作れる。C#も次のバージョンではC#コンパイラがC#で書かれるという計画があります。そのことがもたらす価値の一部分は、TypeScriptが教えてくれます。いや、むしろブラウザ上で全て完結というのは、C#以上の魅力がありますね、正直……。
結論
TypeScriptは、良い言語だと本当に本当に思います。私は、素のJavaScriptも別にそこまで嫌いではないのですけれど、やっぱ、違うなあ、と。なので今後は積極的に使っていきたいところです(CSSもLESSで!)。
言語設計者が同じということもありますが、特にC#erには絶対馴染むと思うので、(linq.jsとセットで)今までJavaScriptとは無縁だった人も手を出して欲しいですね。きっと気に入りますし、視点が変わります。勿論、ネイティブJSerも是非是非触ってみるといいと思います!というか触ってほしいです。
あ、あと、軽く流しましたがVisual StudioユーザーならWeb Essentialsも必ず入れておきましょう。これがあるのとないのとでは、TypeScriptの使い勝手全然違ってくるので、TypeScript試すならば必須です。
linq.js ver.3.0.2-RC, WinRT対応、最新RxJS対応など
- JavaScript linq.js - 12.09/16
RCリリースしました!これでAPI弄りは終了で、あとはドキュメント周りの調整のみといったところです。
ダウンロードはダウンロードボタンからではなく、ダウンロードタブからlinq.js ver.3.0.2-RCを選択してください。というかここからダイレクトに飛んでください。
Beta2から結構立ちましたが、その間に、ノートPCがぶっ壊れたり(今もサポートで修理中、ちょうどうっかり未Pushなコミットが溜まってた状態で逝ってしまったのが痛手でどぅーにもこーにも)、そもそも日本にいなかったり(シンガポールにいましたというか、今は日本にいますが、これからは基本的にシンガポール中心になる感)とか、まぁ色々で色々で捗らずで。
さて、その間でもないですが、プログラミング生放送で8/25にセッションを持ちまして、そこでlinq.js ver.3の紹介をしましたので、スライド・録画ともどもにどうぞ。
Visual Studio 2012はJavaScript関係がハイパー強化されているのですけれど、そのビッグウェーブにフルに乗っかって強力なんだぞ!みたいな。そういったVS2012のパワーとかの部分は、デモの比率が高いので、時間に余裕があれば、是非とも録画を見ていただけると嬉しいです。
あと、こそっとLINQ to XMLのアナウンスを、こそっと、ね。ふふり。
あ、そうだ、スライドに関しては一点、嘘があります。VS2012にはjQueryのIntelliSenseドキュメントの日本語版は入っていません。英語のみです。本当にごめんなさい、これ、確認していなくて、VS2010では日本語訳されたのが入ってたからそうなんだろうなあ、とか思ってたのですが、そんなことはなかったです。予算的な都合なのでしょうか……?ともあれ、申し訳ありませんでした。
更新事項
今回も破壊的変更が入っていて、firstOrDefault, lastOrDefault, singleOrDefaultの引数が変わりました。
// 以前 .firstOrDefault(defaultValue, [predicate]); // これから .firstOrDefault([predicate], [defaultValue]);
です。ようするに引数が逆になりました。predicateが先にあるのがC#のLINQと同じ並び順なわけで、ここだけ、何故かlinq.jsはC#に従ってなかったのですね。理由としてはJavaScriptにはdefault(T)は存在しないのでdefaultValueの指定を強制するために、第一引数に持ってきてやらなければならない。と、当時、3年前は思ってたらしーんですが、別に普通にないならないでnullでいいだろ馬鹿が、むしろ引数がこれだけ違うとか紛らわしいだろクソが。ということにやっと至りまして、変えられるのは今しかない!ということで変えました。
コードスニペット
そういえば3.0.1-Beta2のリリース時にはブログ書いてませんでしたが、そこでコードスニペットを改良したのを入れました。linq.js ver.2に同梱してたものよりずっと良くなってるのでリプレースするといいです。非常に捗ります。というか、もうこれなしで書く気しないぐらいに。
RxJS
Reactive Extensions for JavaScript(RxJS)がオープンソースになりました、完全にソースコード公開です、ぱちぱちぱち。今まではScriptSharpで変換してたような気配だったのですが、完全手書きに移行したようです。
それに伴ってlinq.jsのRxJS連携も、若干手を加えました。ところで、今のところNuGetにあがっているものは、GitHubの最新に追随してません。古いままです。なので、NuGet版だとlinq.jsのRxJS連携は動かなかったりします(ビミョーに中身変わってるんですよ、いやはや……)
今のところ新RxJSに関してはリリースとかも打たれていないので、ステータスがどうなっているのか、よくわかりません。まあ、近日中に、かなあ?どうなのでしょうね。とりあえず、動向には注目、ということで。
WinMD
Windows 8のアプリケーション(Metroとは言えなくなりました!)はJavaScriptでも開発できるわけでして&C++やC#で作成されたライブラリも条件付きというか専用のコンポーネントとして作れば、JavaScriptでも読み込むことができます。 コレクション周り、IList<T>はJavaScriptでは配列として扱えます。なので、これは何もなく普通に列挙できるし、今までのlinq.jsでも扱うことができました。しかし、IEnumerable<T>はIIterable<T>というものに化け、これは独特の列挙の仕方を要求するため、フツーには扱いづらく、また、今までのlinq.jsでも使えませんでした。
が、ver.3.0.2-RCからは、IIterable<T>対応を入れたので、列挙可能です!
// IIterable<T>を列挙できるのはlinq.jsだけ! var iterable = ToaruLib.GetIterable(); Enumerable.from(iterable).forEach();
WSH(JScript)対応といい、Windows固有のニッチ需要に100%応えるのはlinq.jsだけ。いやほんと。
文字列ラムダと無名関数
これは今までもの話なのですが、文字列ラムダに抵抗あるー、というのは分かります。しかし、無名関数を渡すこともできますぜ、というのは、分かって欲しいというか、利用シーンとしては半々なイメージなんですよね。例えばですが二つのJSONをJoinするのに
var jsArrayA = [{ "projectid": 122, "projecttype": "radio" },{ "projectid": 133, "projecttype": "tv" }]; var jsArrayB = [ { "actionid": 1, "name": "kuow", "pid": 122 }, { "actionid": 2, "name": "kplu", "pid": 122 }, { "actionid": 3, "name": "abc", "pid": 133 }, { "actionid": 4, "name": "espn", "pid": 133 } ]; var queryResult = Enumerable.from(jsArrayA) .join(jsArrayB, "$.projectid", "$.pid", function (a, b) { return { projectid: a.projectid, projecttype: a.projecttype, actionid: b.actionid, name: b.name, pid: b.pid } }) .toArray();
これ、全部、無名関数で書くことも可能です。”$.projectid”をfunction(x){ return x.projectid} と書けばいいので。”$.pid”の部分もそうです。でも、それってすごくだるいですよね。
LINQはプロパティ名を指定するだけの無名関数を要求するシーンが多いです。どうせ、JavaScriptは動的言語、コンパイルチェックも働かないのですから、文字列で指定しても一緒でしょう。また、これは、jQueryのセレクターと同じようなものです。そう考えれば、文字列指定にもさして抵抗感はないのではないでしょうか?短くサラッと文字列でプロパティ名を指定したほうが、書きやすいし可読性も高いです。
同様に、最後のJOIN結果を新しいオブジェクトに変換しているところは、文字列ラムダで書くことも可能です。”{projectid:$.projectid, projecttype:$.projecttype,….}”といったように。でも、それって今度は逆にとても見づらくて可読性落ちますよね。長いコード、入り組んだコードになるようならば、素直に無名関数を使ってもらうのがいいな、と思っています。
次回
次は正式リリースです!いつになるかは、いつになるかしらん!8月末が正式リリースのつもりだったのに、一か月遅れでRCリリースですからねえ、んもぅー。ともあれ、間違いなく良い出来になっているので、楽しみにしてください。で、もうその前にRCじゃんじゃん使ってくだしあ。
XboxInfoTwit - ver.2.4.0.4
- XboxInfoTwit - 12.07/29
Xbox.comが内部的にちょっと変わっていて動かなくなっていたので、それに対応しました。多分!
ちょっと今シンガポールに来ていまして、Xbox360の実機がないので動作確認してません!うわぁぁぁ。shoganaiので、ちょっとこちらでXbox360を調達しようか割と考え中。まあともあれ、動いたか動かなかったか報告頂ければ嬉すぃなあ。
linq.js ver.3.0.0-beta, メソッドlowerCamelCase化、など
- JavaScript linq.js - 12.07/20
ずっとやるやる詐欺だったlinq.js ver.3ですが、ようやく始まりました!
トップページのボタンはstableのものになるので、DOWNLOADSタブからver.3.0.0-betaを選んでください。また、NuGetを使っている人はInstall-Package linq.js -Preで入ります。他にlinq.js-jQuery -Pre, linq.js-RxJS -Pre, linq.js-QUnit -Preなどり。
lowerCamelCase化
はい。ようやくJavaScriptらしくなりました。UpperCamelCaseにはC#っぽいとか、キーワードで衝突しなくなるとか、ちょっとした利点はなくもないのですが、そもそも.NETっぽくないほうがいい、というかJavaScriptの世界にちゃんと馴染ませたいという思いのほうが強い。そして、.NETの人以外にも使って欲しくて。
Enumerable.range(1, 10) .where(function(x){ return x % 2 == 0}) .select(function(x){ return x * x});
当然ながら超破壊的変更です。ver.2までのコードは一切動かなくなります。やりすぎですが、しょうがない。痛くてしょうがないけれどしょうがない。さて、ならばとついでにメソッド名の見直しもしました。
Return -> make CascadeBreadthFirst -> traverseBreadthFirst CascadeDepthFirst -> traverseDepthFirst BufferWithCount -> buffer ToString -> toJoinedString Do -> doAction Let -> letBind MemoizeAll -> memoize Catch -> catchError Finally -> finallyAction ToJSON -> toJSONString
これを機に、というかこういう機会じゃないとやれないですから。toStringやtoJSONは、上書きしてしまうとマズいので別名にしています。toStringは、まあそのままなので分かると思うのですが、toJSONのほうは、JSON.stringifyで特別扱いされるメソッドなので、こっそり注意が必要なんですね、というか実際ハマッて気づくのに時間かかりました。
extendTo
prototype.js以降、prototype拡張は悪、でしたが、最近のJavaScriptはfor inで列挙しない形での拡張(Object.definePropertyでenumerable:falseにする)が可能になっています。それを全面に押し出したSugarといったライブラリもあり、確かに便利なのですよね。
さて、linq.jsでは配列などをLINQで扱うためにEnumerable.fromで包んでやる必要があって面倒でしたが、配列からそのままselectとかwhereとかが生えていれば、便利、ですよね?なので、任意に拡張できるようにしました。
// Arrayを拡張する Enumerable.Utils.extendTo(Array); [1, 3, 10, 1000].where("$%2==0").select("$*$");
Enumerable.Utils.extendToを一度呼べば、from不要で直接LINQのメソッドを呼ぶことができます。もしブラウザがObject.definePropertyに対応していなければ、その時はprototypeを直接拡張しています。
さて、LINQのメソッド名とネイティブのメソッド名が被っている場合は、ネイティブのメソッド名を優先して、上書きはしません。例えばjoinとか、concatとか。その場合はByLinqがメソッド名の末尾につきます。joinByLinq、など。
// 名称が被るものはByLinqというプリフィックスがつく [1, 3, 10].reverseByLinq(); // もしくはasEnumerableメソッドを呼んであげればLINQのメソッドのみになります [1, 10, 100].asEnumerable().forEach(function(x, index){alert(x + ":" + index)});
forEachなどは古いブラウザではそのまま、新しいブラウザではforEachByLinqになる、といったようにブラウザ互換性がなくなるので、個人的にはByLinqの形で呼ぶよりかは、asEnumerableを使うことのほうをお薦めします。
Visual Studio 2012でのIntelliSense超拡張
VS2012でlinq.jsを使うと、ただでさえ充実していたIntelliSenseが更に超補完されます。どのぐらい補完されるか、というと、selector関数でオブジェクトの候補が並んでしまうぐらいに。
もはや完全にC#。あまりの快適さにチビる。勿論、↑の図ではFooは文字列なので、x.Foo.で文字列のメソッド候補がIntelliSenseに並びます。動的言語とは思えない超補完っぷりがヤバい。そして入力補完が最大限に活きるように設計されているLINQなので、組み合わさった時の快適度は半端ない。
Chaining Assertion for QUnit
ユニットテストを書く際に、equal(actual, expected)と書くのが嫌いでした。どちらがactualなのかexpectedなのか一瞬悩むし、そもそも外側から包むのがかったるくて。かといってshouldといった、英語的表現なのも冗長なだけで全く良いとは思っていませんでした。そこでC#ではChaining Assertionといった、actual.Is(expected)でアサートが書けるライブラリを作ったのですが、それをJavaScript用に移植しました。
// 流れるように.isと打ち込むだけ Math.pow(10, 2).is(100); // strictEqual(Math.pow(10, 2), 100) // コレクションに対する適用は可変長にカンマ区切りで値を並べるだけ。勿論、配列にも使えます。 Enumerable.rangeTo(10, 15, 2).is(10, 12, 14); // deepEqual(Enumerable.rangeTo(10, 15, 2).toArray(), [10, 12, 14]) // LINQと組み合わさることでコレクション系のチェックが遥かに容易になる! [1, 5, 10].all("$<12").isTrue(); // collection assertion with linq.js!
といった感じに書けて、超楽ちんです。使うにはlinq.qunit.jsを別途読み込んでください。
その他
createEnumerable, createEnumerator, createLambdaといった、自作Enumerableメソッドを作るための道具を外部公開するようにしました。是非作っちゃってください。
Enumerable.Utils.createLambda Enumerable.Utils.createEnumerable Enumerable.Utils.createEnumerator
更に、メソッドも追加されています。
Enumerable.defer asEnumerable merge choose isEmpty distinctUntilChanged weightedSample log
それらの細かい使い方などは追々書いていきます。また、merge, zip, concatは複数のシーケンスを引数に受け取れるようになりました。
そして、C#では、以前にneue cc - LINQのWhereやSelect連打のパフォーマンス最適化についてという記事を書いた通り、Where連打やSelect連打、それにWhere->Selectといったよくあるパターンに対して最適化が入っているのですが、それをlinq.jsでも再現しました。なので、Where連打などによるパフォーマンス劣化が抑えられています。また、頻出パターンのWhere->Selectで高速化されたのはかなり大きいと思っています。
それに加えてrange, rangeDown, rangeToといったよく使う生成関数の速度を大幅に上げました(以前はtoInfinity().take()で生成していたのを、独自生成に変更したため)。
なので全体的にパフォーマンスも向上しています。
それと最後に、jQueryのプラグインとしてのものは今回からやめました。なんか混乱するし意味ないな、と思ったので、jQueryとの連携はlinq.jquery.jsによるtoEnumerable/tojQueryを追加するファイルのみとなっています。RxJSに関しても最新版のRxJSと連携できるようにしました(linq.rx.js)
今後
VS2012に対するIntelliSenseの充実化がまだ1/5ぐらいしか出来ていないので、それの充実が優先です。あと、リファレンスやサンプルが書けてないので追加。それらが出来たら、いったんver.3として正式公開します。プログラミング生放送勉強会 第17回@品川 #pronama : ATNDで話すつもりなので、その日、8/25までには正式公開を目指します!というわけで是非是非聞きに来てください。
あ、あとnode.js用にnpm公開も、ですね。
XboxInfoTwit - ver.2.4.0.3
- XboxInfoTwit - 12.05/13
Xbox.comが内部的にちょっと変わっていて動かなくなっていたので、それに対応しました。前々から報告されている不具合などに関する修正などは一切入ってません、すみません。最近割と普通に忙しくて全く手をかけられない状態でして……。落ち着いたらその時には必ず。
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を生成している部分に直書きで挟んでやればヨシ。スクリプトの軽快さは良いですね。なので設定というか読み込むファイルも先頭のほうで普通に直書きで指定しちゃっております。
そのまま使ってもいいんですが、ビルド後に実行するコマンドラインに指定してやれば一切の手間暇なく常にフレッシュ。おお、素敵。