Archive - Software

TwitterTLtoHTML ver.0.2.0.0

TwitterのBasic認証の有効期限が8月いっぱいだったのですよね。それは知っていて動かなくなるのも知っておきながら、実際に動かなくなるまで放置していたという酷い有様。結果的に自分で困ってました(普通に今も使っていたので)。というわけで、TwitterTL to HTMLをOAuth対応にしました。認証時のアプリケーション名がTL to HTMLなのですが、これは「Twitter」という単語名をアプリケーション名に入れられないためです。面倒くさいねえ。

OAuth認証は@ugaya40さんのOAuthAccessを利用しています。XboxInfoTwitでは自前のものを使っていたのですが、どうしょうもなく酷いので自分で作り直すかライブラリを使うか、でズルズル悩んで締切りを迎えたのでライブラリ利用に決定ー。使いやすくて良いと思います。

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

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

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

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

インストール

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

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

プロジェクト作成

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

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

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

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

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

エラー表示

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

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

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

コードフォーマット

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

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

デバッグ

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

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

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

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

入力補完/日本語jQuery

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

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

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

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

vsdocについて

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

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

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

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

その他設定など

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

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

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

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

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

まとめ

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

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

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

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

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

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

CodePlex - linq.js - LINQ for JavaScript

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

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

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

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

GroupByでのcompareSelector

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

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

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

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

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

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

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

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

Lookup

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

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

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

Dictionary

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

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

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

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

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

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

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

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

Share, Let, MemoizeAll

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

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

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

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

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

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

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

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

まとめ

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

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

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

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

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

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

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

linq.js ver 2.0 / jquery.linq.js - Linq for jQuery

無駄に1280×720なので、文字が小さくて見えない場合はフルスクリーンにするかYouTubeに飛んでそちらで大きめで見てください。というわけで、動画です。linq.js + Visual Studio 2010で補完でウハウハでjQueryプラグインで世界系です。ここ最近、RxJS、JSINQとJavaScript系の話が続く中で、ふと、乗るしかない このビックウェーブに、という妄念が勢いづいてlinq.jsをver.2.0に更新しました。

linq.js - LINQ for JavaScript

内部コードを全面的に変更し、丸っきり別物になりました。破壊的な変更も沢山あります。名前空間が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と一緒に使う

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を使おう - 複雑な検索処理を簡潔に記述する「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が動作するファイルがあること、などなど「実用的に使う」ことを強く意識して作っています。手前味噌なのでアレですが、他のどのライブラリよりも使える度は高いと思っています。

更新が微妙に止まっているのですが、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絡みです、ひたすらに使い倒して、有用な使い方を紹介していけたらと思っています。

XboxInfoTwit - ver.2.2.0.0

今回の更新は、HTMLをXMLに変換するライブラリをTidy.NetからSGMLReaderに変更しました。数日前にSGMLReaderでLinq to Html最高なんてエントリーを上げていたので、早速実戦投入というわけです。内部コードが割と変わったため、ver2.1系列から2.2へとアップ。利用者的にはぶっちゃけどうでもいい話です。すみません。

ユーザーに関係ある変更点は、先日発売されたばかりのBioShock2で実績が取得出来てなかったので、それを直しました。私はBioShock2でしか確認していないのですが、「カルドセプト」や「のーふぇいと!」も実績が取得出来ないという報告が上がっていたので、今回の修正によって取得出来るようになった、かもしれません。分かりません。カルドセプトやのーふぇいと!を持っている方は実績取れたよー、と教えていただけると助かります。一応Twitter検索で追っかけてはいるんですけど、最近投稿量が多くて(認証者数は1400行きました、ありがとうございます)全然目を通せていなかったりして。

追記:「カルドセプト」、「のーふぇいと!」ともに実績取得出来ているようです。確認していただいた方、ありがとうございました。

ver.2.1.0.1

Jewel Questで「未知のエラー」が発生する件を修正しました。言い訳がましいですが、これXbox.comのバグですよ! Netflixの時もなんじゃこりゃ、と思ったんですが、今回は”Insert translated text here”です。明らかにオイオイオイオイしっかりやれよ、って感じにアレなメッセージが浮かび上がってます。

こんなのがステータス画面のソースを開くと確認出来ます。いやまあ、だから何だって話ではあるのですけど。イレギュラーなことやってるのはコッチですからね……。

さて、ところで今回の不具合は1月上旬に報告を貰ったのに対処したのが1月ギリギリってどういうことよ、すみません本当にゴメンナサイ。不具合情報の報告は大変ありがたいのですけど、ちゃんとそのありがたさに応えなきゃダメですね、私。特に今回は確認も修正も全く難しくないところなので、しっかりしろよ、というお話でして。今後はしっかり対応していきます。

ただ、既知の不具合である、一部の人がログイン段階でコケるという件は全く手付かずです。いやー、自分のとこに環境ないとさっぱり分からん。あ、あとカルドセプトでステータスが反映されない件も放置中です、すみません。気が向いたら、というかソフト入手したらそのうち……。

デジカメ写真の半リアルタイム確認システムの構築

写真撮る→PCに即座に転送される→ビューアーで転送された画像が自動的に開かれる→つまり撮影画像のプレビューがPCの画質で出来る、わーい。ふむ、何のこっちゃ。というわけで実際に使っている風景を動画をで撮ってみました。ケータイ画質で、風邪薬のビンを取るという適当なものですが、ただの説明なのでご勘弁を。画像転送に少し時間かかってるのでリアルタイムとは言いませんが、我慢できる範囲には収まっていると思います(もう少し速く、とは思いますが)

私は写真撮影に関して完全に素人で、ホワイトバランスがデタラメだったり露出があっていなかったり、そもそもピンボケだったりと失敗写真を繰り返しています。デジカメの液晶ではちゃんと撮れているように見えたのに!そんなわけで、撮ったものをちゃんとした画面で即座にモニタリングして、設定を煮詰め直すなり撮り直すなりが出来ればいいな、と思っていました。それが出来たら、特に室内での小物や料理撮影には便利だな、と。もちろん、Rawで撮って後から調整すれば良いという話もありますが、さすがにRawは手間がかかりすぎます。もっとカジュアルに付き合いたい。

Eye-Fi

Eye-Fiをご存じですか? Eye-Fi Japanに解説がありますが、無線LANを内蔵したSDカードで、写真を撮るとカードを抜くことなくその場で対応するオンラインサービス(はてなFotolifeやflickrなど)に送信してくれる、というものです。別にそんなに写真公開することなんてないし、そもそも外行かないしでイラナイよなー、なんて考えていたのですが、よーく解説を見ると、PCに送ることも出来るではないか。ということは、撮る→即座にPCで確認出来る。が実現出来る。こ、こんな当然のように思えることに今まで気づいていなかったなんて、悔しい。

ファイル監視ソフト

画像を自動でPCに転送出来る、となると、あとは転送された画像を自動で認識して画像ビューアーを起動するだけです。ちょっとソフトを探してみたのですが、良いものが見当たらない。まず、数分間隔でチェックするものは却下。何故なら、画像が転送されたら即座に起動してくれなければ意味がないから。他にもゴテゴテと機能が多すぎたりと、こういう単純な用途にフィットする監視ソフトが見当たらなかった。んー、ないなら自分で作ればいいぢゃない。というわけで、自分で作りました。シンプルなファイル監視ソフトを。

起動するとタスクトレイに常駐して、設定したフォルダを監視し、「新しいファイル」が作られると指定したアプリケーションにファイルパスを渡しながら自動的に起動します。例えば、指定フォルダをEye-Fiで画像が保存されるフォルダを指定し、実行するアプリケーションに画像ビューア(IrfanViewだったりPicasaだったり)を指定すると、写真を撮る度に画像が自動的に開かれる。ようするに冒頭の動画のような感じになる。というわけです。

アップロード

ついでに、Webサービスへのアップロードですが、私の場合は以前作成した半自動はてなフォトライフアップローダーを使ってワンクリックでアップロードしています。実行すると、事前に指定したフォルダの最新画像一枚だけをアップロードするプログラム(今回、少し更新してサブディレクトリもオプションで含められるようにしました)。なので、指定フォルダとしてEye-Fiが転送する画像フォルダを指定しておけば、Eye-Fiから転送されてビューアで開かれる画像を見て、採用!と思ったらクリックするだけ。それでアップロード完了。とてもお手軽です。

Eye-Fi Pro

そんなわけで、無線LANがインターネットにつながっている必要はなく、PCと一対一に通信出来ればそれでいい。のですが、残念ながら現在日本で販売されているEye-Fiカードは一対一では転送出来ません。海外で既に発売されているEye-Fi Proというモデルではアドホックモード(無線LANを搭載したデバイス、ノートPCとかと直接転送可能になる)が使えます。これがあれば、撮影→ノートPCの画面でプレビューが山でも川でも居酒屋でも、どこでも出来るのに!というわけで、早く発売されて欲しいです、Eye-Fi Pro。

GXR

Eye-Fiは大抵のカメラで使えるようなのですが、たまーに使えないカメラもあるようです。私が以前使っていたカメラは、残念なことにそのたまーに使えないカメラ、に該当してしまったようです。しょうがないので、Eye-Fiのためにカメラを買い換えました。ついでのついでなので合体機構がそそるGXRを購入。GXRは素晴らしいよ!何といってもEye-Fiが使えます(笑) 画質良し、操作感良し、そして何よりも(一眼画質のカメラとしては)軽い。よって、こういったお手軽プレビュー&アップロードには大変適している、気がします。一昨日来たばかりなうえに、私は写真ド素人なので詳しい評価は出来ませんが、かなり気に入ってます。

TwitterTLtoHTML ver.0.1.0.0

ノートPCを買いました。完全デスクトップ至上主義者だったというのに!あれです、あんまり引きこもってばかりいるのもよろしくないので、ノートPCさえあれば勉強会とかも出れる!のかどうかは、そもそもなくても出れるよねえ、あっても出れないよねえ(私の非コミュ脳的に) などと思いつつも、まあそんなこんなで買いました。流行りのCULVノートって奴です。Visual Studio 2008が思っていたよりも遥かに実用的な速度で動いていて、そう、こんなんでいーんだよ、とか思ったりなどした。けれど、VS2010は絶望的に動かなかった。重過ぎる。世の中厳しい。

引きこもり解消目的の他にもう一つ、常時起動の半サーバー用途というのもあります。ストリームAPIを監視したxboxinfotwitusersリストへの追加プログラムを常時デスクトップPCで振り回すのもカッタルイというか消費電力的に無駄なので、低消費電力なノートPCへ退避させよう、と思ったわけです。そもそも他にも、はてなついったー同期ツールだのXboxInfoTwitだの、PC常時起動を前提のアプリを幾つか公開しているので、調度良いということで。

んで、本題。常時起動PCがあるなら、過去ログも常時起動で定期的に取得して、差分をHTMLに残せばいいよね!それをDropboxなんかの共有フォルダに保存するようにすれば、取りこぼしもないし、何処からでもログを参照できるしで最高ぢゃん(そこで本当に自宅鯖にしてネットワークに公開する、というのは手間がかかりすぎるので超却下)。というわけで定期起動実行用のモードを追加しました。前回取得からの差分のみを、yyyy/MM/dd_HHmmssの形式(/はフォルダ)で保存します。今まで通り、過去800件取得モードも残してあります。

定期取得でやりたい場合は、タスクスケジューラに突っ込めばおk。タスクスケジューラは柔軟に設定出来る分、とっつきづらくて面倒くさいんですねえ。でも、例えば「バッテリ電源の時は実行しない」とか素敵オプションが色々用意されているので、使うといいと思います。トリガを大量に設定しておいて、18-24とかの流速の激しい時間帯は更新間隔短め、0-9とか静かな時間帯は更新間隔長め、12時のお昼休憩の前に一度まとめて読みたいので12時ジャストに設定。とか色々と考えられますので適当に気に入る設定を探ると良いんじゃないかと思われます。

あと一応、TinyUrlとかのデコード機能も入れておきました。実装は超手抜きで、Urlを片っぱしから WebRequest.Create(url).GetResponse().ResponseUri.AbsoluteUri; しているだけです。んま、問題ないでしょう、多分。普段Echofonで短縮Urlのまま表示されていただけに、こうして展開された形で見れると、いかに短縮Urlがイライラさせるものなのかよーく分かりますな。投稿時に必須なのはしょうがないのですけど……。

それと、今回からは初回設定は対話式ウィザードで行うようにしてます。あと、パスワードはそのPCでのみ復元出来る、という形で暗号化されます。設定ファイル直書き換えで一番嫌なのは、パスワードを平文で置く、ということなので、それを避けるために、ですね。書き捨ての小さいコンソールアプリなのでいっかー、と最初思ったんですが、やっぱり気になりました。

最後に、バグフィックス。二重でHtmlエンコードしてた部分を直しました。TwitterからのXMLは既にHTMLエンコードされている状態なので、それをそのままXElementに流し込むと二重でエンコードされてしまいます。なので、一旦デコードしています。この辺は結構よくミスしてしまうんですよねー。取得したものがどんな状態なのか、利用するクラスがどういう動作をするのか、ともにちゃんと把握していないとハマリがちです。

ちなみにまるで利用者がいる風な口で紹介していますが、ダウンロード数は超絶少ないので利用者なんていませんよ! 完全に自分用ですな。

2009/12/09 追記

ダウンロード先ファイルが古いバージョンのままでした……。今、直しました。ただでさえゼロに近いダウンロード数だったというのに、こうして使ってくれるかもしれない/コードを見てくれるかもしれない人を失ってしまう……。

TwitterのTL過去ログをHTMLにするツール

Twitterの他の人のポストは全部読みたいと思っています。数千もフォローしてるアルファーツイッタッターでは無理でしょうけど、せいぜい百ちょいぐらいなら全然いけるわけです。と、思っていたのですが、たかだか200を超えたところで、ん、無理……?と思える感じになってきてしまいました。ツール的限界で。Webから過去ログを見ようとすると、限界点に到達してしまって未読があるのに過去ログが見れない状態になってしまって。ていうか、そもそもWebでログを見るというのはダルい。まあ、ないですよね。私がTwitterで使っているツールはEchofonで、これは過去ログ見るのに適さないし全然昔の見れないし、というわけでどうしたものかなー、と思っていたんですが、作ればいいわけですよね、過去ログ閲覧専用Twitterクライアント。

と、考えてはみたものの、そもそもわざわざツール作るまでもなく、ログをHTMLで吐けばいいんじゃね?と気付いた。YesYesYes。流し読みなら、むしろへっぽこ専用ツールよりもブラウザのほうが見やすいし。家でガッとHTML取得しといてモバイルに転送して電車でゆったり見る、とか出来るし。というわけで、可能な限り過去ログを掘ってHTMLに吐きだすプログラムを書きました。可能な限り、といってもAPI制限の都合上で最大800件まで、のようです。うーん、これじゃあ半日ぐらい前、程度ですよねえ。18-24時とかだと一瞬で吹っ飛びそうかも。3000件ぐらいまでは欲しいとこなのですが……。なお、API消費はたった4か5なので安心です。一回につき200件取れるので。

デザインはCSSで行えます。例えばimgのwidthとheightを0pxにすればアイコン表示を消せます。これで学校や会社で見る時にアニメアイコンが並んで恥ずかしい思いをしなくて済む! あとまあ、デフォルトのCSSはショボい(私がCSSの知識ないので……。float良く分からん、高さ揃わない、50pxで決め打ち!とか)ので、適当に改良して使ってください。

あと、コード(C# 3.0)も同梱してあるので適当に見て突っ込んでくださいな。HTML組み立て部分はLINQ to XMLです。

var urlPattern = new Regex("(s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+)");
var xhtml = new XElement("html",
    new XElement("head",
        new XElement("link", new XAttribute("rel", "stylesheet"), new XAttribute("href", "style.css"))),
    new XElement("body",
            new XElement("ul",
            EnumerateHomeTimeline(username, password).Select((t, i) =>
                new XElement("li", new XAttribute("class", (i % 2 == 0 ? "even" : "odd")),
                    new XElement("div",new XAttribute("class","name"), t.ScreenName),
                    new XElement("div",new XAttribute("class","date"),t.CreatedAt.ToString("G")),
                    new XElement("div",
                        new XAttribute("class","image"),
                        new XElement("img",new XAttribute("src", t.ProfileImageUrl))),
                    new XElement("div",
                        new XAttribute("class","text"),
                            urlPattern.Split(t.Text).Select(s=>
                            {
                                var href = urlPattern.Match(s);
                                return (!href.Success) 
                                    ? (XNode)new XText(s)
                                    : new XElement("a",new XAttribute("href",href.Value),href.Value);
                            })))))));

えーと…… 汚い、ですね!それでも、このLINQ to XMLの関数型構築がなければどれだけ悲惨なことになっていたか!やはりLINQ to XMLは素晴らしい。さて、しかし困ったのがリンクのaタグ付け。文字列で扱っていれば普通に置換すれば済む話なのですが、XTextにそれを放り込むとタグはエスケープされます。最初驚いたのですが、考えてみると当然ですね、XMLとして不正なものは許されないので。しょうがないのでSplitしてXMLとして組み立ててやりました。

json/xmlを拾ってきてHTMLに整形するだけなのだから、JavaScriptで書いてうぇぶあぷり、的なものにしたほうが利便性とか何とかかんとかが良好なんじゃございませんこと?とか思わなくもなかったのですが、C#、楽なので、ほんと。良い言語なんですって。

ver.2.1.0.0

未知のエラーが発生する原因の一つを解消しました。私の確認出来た範囲では、フレンドの中にNetflixを使っている人がいると100%エラーが発生するようでした。原因はプレイ中状況が<Translated text>で、この<がエスケープされてないせいでタグとして認識してたせいでパースに失敗してるせいでした。適当に検索したところTranslated textはちょっと特殊な状況表示?で他のゲームでも出現するようですね、何だろう、翻訳しようと思ったけどまだ出来てませんって感じでしょうか(笑)

で、えーと、これはXbox.comが悪いですよ、ほんと、Microsoftはもっとしっかりサイト作って欲しいなあ。いつぞやかの実績暴走の件だってそもそも……。と、言ってみたところでユーザーからはプログラムがタコなせいにしか見えないわけですし、Xbox.comはXbox.comで、イレギュラーなアクセスをしてる輩のことなんて別に考える必要はないわけで、やっぱり悪いのはプログラムですね、あはは。

さて、今回は<Translated text>を丸ごと置換するという頭悪すぎな方法での応急処置をしたのですが、今後も平然と<がエスケープ抜きで登場するようなケースは、ありそうですね……。というわけで、何とかすべきところではあるのですが、汎用的な置換表現を作るのはほぼ不可能だし、全てに対応しようにも如何せん何処に出現するかも不定すぎて無理げ。別の問題が出た時にまた考えることにします。

機能追加が一つ。指定文字列が含まれる場合には投稿しない、という機能を足しました。例えば「Xbox 360 ダッシュボード|Halo Waypoint」にすれば、ダッシュボードとHalo Waypoint再生時は投稿しないようになります。なお、大文字と小文字やスペースの有無を完全に区別しますので、利用するときは一度Twitterに投稿されたものをコピペすると良いと思います。なお、ver.1にあった「ダッシュボードは無視」機能に似ていますが、ver.2のものは起動時投稿設定にも適用されるため、100%、ver.1と同じというわけではありません。うーん、ver.1の起動時設定のみ特別扱いってのがどうかなー、と思っていたので今回の仕様に変更されたわけですが、どうなんでしょうねえ。

ver.2.0.1.0

一部タイトル、例えばアジア版GoW2で実績が取得出来ないという不具合を修正しました。あと、今現在、私の方でもたまに「未知のエラー」が出るのは確認出来ているのですが、ちょっと原因が掴めていない状態なので修正にはもう少し時間がかかりそうです。それと、そもそもXbox.comへのログインに失敗するというのは全く分かってませんので、もう少しどころじゃなく時間がかかりそうです。

そういえば説明を忘れていたのですが、ver.2からver.1にあった「投稿の際ダッシュボードは無視」機能は削ってしまいました。これは、どうやっても綺麗に多言語対応と混ぜることが出来なかったので……。日本語だけに限定すれば決めうちで簡単なのですけどね。利便性的には多言語対応なんかよりもこっちのほうが遙かに上だろ!と突っ込みたい気持ちはとても分かりますが、そんなこんなな事情なので復活させることは恐らくありません。

もう一つ、ver.2からLiveのステータスが離席中になった際もオフライン扱いにしちゃっています。ver.1では中途半端な無視の仕方をしていて、潜在的なバグの危険性があったので、すっぱりとオフラインということにしてしまいました。本体を10分放置しているとスクリーンセーバーが動いて、Liveのステータスも自動的に離席中になるようなのですが、もし本体放置で離席中になるのを拒否したい場合はスクリーンセーバーをオフにすればLiveステータスもずっとオンラインのままになります。スクリーンセーバーの切り替えは本体設定から「システム設定→本体の設定→画面→スクリーンセーバー」で入れます。

ver.2.0.0.0

XboxInfoTwitの認証数が岡本641本吉起を超えた記念、というわけでもないのですが大幅に変更しました。例によって全然テストしてないので動かないとか色々あるかもなので、生暖かい目で見守ってください。というか、ボソッとTwitterでxxで動かねえ、とでも言って貰えると非常に助かります。

今回の更新の主な内容は、クローラーを刷新しIEを使用しなくなった。です。それによって「メモリ消費量激減」(というか前が多すぎた、というか完全にメモリリークしてた) 「スクリプトエラー消滅」「IEでのログイン状態に左右されない」「ページ遷移のクリック音UZEEと無縁」などなど、まあ、これで安心して使えるかと思います。環境依存的に動かなかった人も動くようになった、はず、きっと。そんなこんなで、今回から中身が全く別物になっているので、環境依存、もしくはバグによる動かないケースが(また)増えそうなので、その辺は見つかり次第早めに対処したいと思います。当面は不安定かもしれませんがご了承ください。

挙動の変更としては、実績解除の投稿が必ず行われるようにしました。今までは実績解除後、投稿されるまでの間にXbox360の電源を落としたり別のゲームに変えてしまったりすると解除の投稿を行わなかったのですが、今回からは、電源を落としても別のゲームに変えても実績解除の投稿を行います。ちなみにまだ一回も実績解除を試してないので(デバッグ用にデータをごそごそ弄って解除したフリ、ぐらいはやりましたが)本当に上手く動いてくれるのかは謎です。

あと、エラーメッセージが親切になりました(今まで一律に通信エラーで理解不能だったので) でもタイミング次第では平然と「未知のエラー」とかいう素っ気ない応答しか出しません。酷い。この辺は追々直していこうかな、とは思ってるのですが。

機能追加その一、別言語からの取得が可能に!今まではja-JPだけでしたが、英語ならen-USを、台湾語(中国語?)ならzh-TWを指定することによって、他の言語のデータが投稿されます。別にja-JPしか使わないとは思いますが、将来的にアプリケーション自体を多言語対応にして海外版もリリースしたいなあ、と思っているので(そのタイミングでコードも公開しようと考えてます)そのための下準備の一つです。

機能追加その二、ハッシュタグの自動付加。新しくプレイしたタイトルはタイトル名が記録されて、設定画面のハッシュタグタブの一覧に自動的に追加されます(任意での追加は不可能です)。ここでリストに、例えば「モダン・ウォーフェア2」だったら「MW2」と入力すれば、モダン・ウォーフェア2をプレイ時の投稿全ての末尾に「 #MW2」が付加されます。#に関しては付けなくても自動的に付けます(なのでハッシュタグとしてではなく、フッタとしての利用は現状不可能です)

機能追加その三、バルーンによる投稿通知。私的にはどうでもいいと思ってごほごほ。

2.0.0.1

例によって不具合発覚。20-30分ぐらい使ってると未知のエラーで死ぬようです。あまりにもの未知のエラー祭りは酷すぎた、のでとりあえず様子見で暫定的に対処してみました。うまくいってるかは不明。たかだか10数分間連続利用のテストすらしていないという!すみませんすみません。とりあえず今日は発売日に買ったけど全然プレイしてない(実績数がそれを物語ってる)Fallout3(OBLIVIONは超はまったのにFallout3はさっぱり琴線に触れず)をじっくりプレイしながらテストします、はい。

ver.1.3.0.8

暴走してしまいました。大変申し訳ありませんでした。Xbox.comが半メンテナンスで、壊れたデータを放出していたのですが(例えばFallout3の最大実績が620になったり)、それを取得して解析していた結果、実績を超連続投稿するということが発生しました。今回のものは暴走抑止用の暫定対策版となっています。しかし確実に防げる保証はないので、利用はXbox.comが安定してからにしてください。また、お願いなのですがXboxInfoTwitが不調な場合は、またXboxInfoTwitのクソが不安定だぜ、と思うのは当然なのですけど、少しだけXbox.comのほうも疑ってあげてください。そして、Xbox.comが怪しかったら、その日は利用を控えるという形でお願いします。これからは、データが怪しい場合は弾くような処理も増やしていこうとは思いますが、それでも全ての怪しいケースを弾けるわけではないので。

暴走抑止のほか、とりあえず実績連続投稿の最大数は10に設定しました。5だと、場合によっては少ないケースも出てきそうなのでとりあえず10で。それと、今回のアップデートは全くテストしていないので、そもそも正常動作するかも分かりません。その辺は、Xbox.comが落ち着いたら見ていきたいと思っています。

あと、責任はとても重く感じています……。公開停止しようとも思ったのですが、誰もがこのサイトを見に来ているわけでは当然ないので、まずは修正して新しく起動する人が、自動アップデートで最低限の回避をするのが第一だと思いました。今後の公開停止ですが、既に相当数の利用者がいる状態なので、公開停止にしてメンテ放置するよりは、問題が起こった際にちゃんと面倒を見る方が重要だと考え、当分は公開を続けることにします。

ver.1.3.0.9

連続ですがまた更新。1.3.0.8では実績解除自体が100%投稿できない状態になってました。風呂に入って頭冷やしてたら思い違いに気づいてああああああ、となりました、はは。それと、暴走の原因らしきものが見えたので(原因自体はXbox.comのデータ壊れなのですが、どの部分がどういう風に壊れていたのか、というのが私自体が遭遇してないので想像でしかないのですよー) とりあえずそれへの対策を重点的に追加してみました。原因が見込み違いだったり、他の原因だった場合は、まあ、しょうがない。そういえば今日ダッシュボード機能追加なんですね。毎回、機能追加前はXbox.comも合わせてドタバタしますが、しかし今回ほど酷いこともなかった。ちなみにもう一つの実績解除ツールも暴走していたので、今回のは本当に本当に不測の事態というかXbox.comのデータの壊れ方が誰にとっても想定外でした。天下のMSなのだから、メンテ時でもしっかりやってくれ、というのは贅沢ですかね。

Prev |

Search/Archive

Category