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

ParseOrDefault

match があれば TryParse いらないんじゃないか - 予定は未定Blog版という記事を見て、outとかrefは確かにしょっぱい。そういえばF#はTryParseはTuple返しで、おまけに let success, value = int.TryParse "123" という自然な多値返しが出来てCOOLなんだよねー。などと思ってC#でTuple返しにしたものを書いたりしたのですが、どうもしっくりこなくて延々と弄っているうちに明後日の方向へ。

さて、で、「数値に変換できるなら変換して返し、できないなら -1 を返す」というシチュエーションは大変多くて、それにoutを使って組み上げるのは、とてもかったるい、大変避けたい。かといってStringへの拡張メソッドを大量に生やすのも、IntelliSense的な観点からして抑えたい(それにしてもT4 Template使って生成ってのは面白いですね)。なので、汎用的に使えることは捨てて、上述のシチュエーション、変換出来るなら変換して、出来ないなら指定したデフォルト値を返すことにのみ絞って、拡張メソッドにしました。Stringに生やすのは抵抗感があるって場合はUtil.ParseOrDefaultとかにしてもいいと思います。

using System;

static class Program
{
    static void Main(string[] args)
    {
        // 例えばURLのクエリストリングのパースをしたい時とかありますよね!
        // num=100にint.Parse(num)はnum=hugaが来たら死ぬのでデフォでは0にしたいとか

        var r1 = "100".ParseOrDefault(int.TryParse, -1); // 100
        var r2 = "huga".ParseOrDefault(int.TryParse, -1); // -1

        // 勿論、int.Parse以外でも何でもいけます
        var r3 = "2000/12/12".ParseOrDefault(DateTime.TryParse, DateTime.Now);
        // デフォルト値を省く時は型の明記が必要
        var r4 = "2000/12/12".ParseOrDefault<DateTime>(DateTime.TryParse);
    }
}

public static class StringExtensions
{
    public delegate bool TryParse<T>(string input, out T value);

    public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse)
    {
        return input.ParseOrDefault(tryParse, default(T));
    }

    public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse, T defaultValue)
    {
        T value;
        return tryParse(input, out value) ? value : defaultValue;
    }
}

out付きの汎用Funcはデフォルトでは定義されていません。なので、自前で定義する必要があります。この辺の話は@takeshikさんのスライドわんくま東京#38 LT 「Func<> と ref / out 小咄」が詳しいのでどうぞ。こんなデリゲートは単体では使うことは滅多にないでしょうから、static classの中に閉じ込めておくことで名前空間を汚しません(笑)

引数にT defaultValueを要求することで、型推論が働いてTryParseを渡す際に明示的な型付けが不要になります。C#の型推論(と言っていいのかな?)がScalaやF#に比べて弱いのは事実なので、ここは、C#でも自然な形で扱えるような誘導、設計が大事です。型を書いたら負けだと思っている、みたいな。まあ、どんなに頑張っても負けるときは負けるので、その時は潔く書くことにします。

ちなみに、ParseOrDefaultは最初はこんな形でした。酷過ぎる。私は骨の髄まで関数型言語脳ならぬLinq脳なので、とにかくまず無理矢理にでもLinqで処理する方法を考えて、出来上がってふと冷静に眺めると、普通に書けよ馬鹿、ということに気付いて普通に書き直すというサイクルを取ってます。馬鹿すぎる。DbExecutorで紹介したyield returnで一個のみの列挙を返すというのを、やたら使って見ちゃったりするところが麻疹。Repeat(value,1) と Repeat(value,int.MaxValue)があればLinqは何でも記述出来るんです病。

DbExecutor - Linqで操作しやすいSQL実行ライブラリ

前回の記事を書いたところ、ついったで素敵な突っ込みを頂けたので、それを元にもう少し練り直してライブラリ化し、CodePlexに公開しました。ライブラリといってもAnonymousComparerと同じく単純なものですので、ソースコード一本のみ。ご自由にお使いください。例によってCodePlexでの英語がヤバい(小学生レベル、とりあえず何でもforつけておけばいいだろ、的な)ですね、世の中厳しい。

Linq to Sqlと名乗りたいところなのですが、本物がありますから名乗れないー。以前はIQueryableじゃないものをLinq to Hogeって言うのはどうよ、なんて思っていたのですが、考えてみるとLinq to XmlもXMLをIEnumerableベースに処理しやすいような構造を持たせたクラス群にすぎず、別にIQueryableは関係ない。Rxもそうで、あれはIEnumerableでもIQueryableでもなく完全に独立している、けれど、Linq to Events。ようするにLinq的な操作が出来ればLinqなわけです。というわけで、これはSqlをIEnumerableベースで処理出来るようにしたLinq to DB。でも本当に超絶薄いラッパーにすぎないのであんまカッコつけた名前付けるのも恥ずかしくDbExecutorという極々普通の名前に落ち着きました。「Linqで操作しやすい」とかいう釣りタイトルをつけてますが、ただたんにIEnumerable返すというだけです。SQL周りは真剣に追いかけると無限泥沼になる気がする(Entity Framework、そして更にその次へと……?)。追いかけてみたいですが、まあ、まずは、一歩目から。

何で作ったかと言うと、SQLを扱っていて嫌なusing地獄を殺したかったから。

using (var conn = new SqlConnection("connectionString"))
using (var cmd = conn.CreateCommand())
{
    conn.Open();
    cmd.CommandText = "select ....";
    using (var reader = cmd.ExecuteReader())
    {
        foreach (IDataRecord item in reader)
        {

ちょっとクエリ呼びたいだけなのに、普通にこの量、このネスト。こんなんだからLLの人に馬鹿にされてしまうんだよ。で、ふと思ったのは、この図式ってもしかしてWebRequestと同じですか?

string html;
var req = (HttpWebRequest)WebRequest.Create("http://google.co.jp");
using (var res = req.GetResponse())
using (var stream = res.GetResponseStream())
using (var sr = new StreamReader(stream))
{
    html = sr.ReadToEnd();
}

ちょっとWebからデータを取得したいだけなのに狂ったようにusingを重ねなければならない!そしてもう一つ嫌なのが、変数に渡す際に、usingのスコープが絡むので場合によっては外で定義しなければならないこと。string html;だってさ。嫌だ嫌だ。別にvarが使えないから嫌だと言っているわけじゃなくて(半分はそうなのですが)、代入位置と宣言が離れるのは可読性が落ちます。あとは、単純に不恰好ですしね。そんなWebRequestですが、WebClientという簡単に使えるものが用意されているので、普段はこっちを使うわけです。

var html = new WebClient().DownloadString("http://google.co.jp");

素晴らしい! 私はWebClientが好きです。簡単なのは良いこと、を体現していますから。ネット上のサンプルがやたらとWebRequestを使うものばかりなことを嘆きます。WebRequestとWebClientでCookie認証をする方法とかいう記事を書いたりと、必死に普及に励んだりしていますが中々どうして焼け石に水、ていうか確かに少し凝ったことをやろうとすると面倒くさいのは否めませんね……。

というわけかで、偉大なるWebClientを見習って、SqlConnectionの一連の流れを抹殺するラッパーを作ってみました。プリミティブなAPIなんて触りたくないっす。プリミティブなものは魅力どころか穢れたものに見えてしまうので、可能な限り隠蔽してやりたいのです。本当は生のSQLだって触りたくないんですけどね……。さて、目標は、簡単に使えることと、usingが極力表に出ないようにすること。とりあえず利用例から。PersonTableというAgeとFirstNameとLastNameが格納されてるテーブルからデータを引っ張ってきます。

// ただの入れ物
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

static class Program
{
    static void Main(string[] args)
    {
        // 実行→即Closeの場合静的メソッドを用いる(Regexと同じ感覚で)
        var maxAge = DbExecutor.ExecuteScalar<int>(new SqlConnection("ConnectionString"),
            @"select max(Age) from PersonTable");

        // 接続を維持して複数回実行する場合はインスタンス生成で
        using (var executor = new DbExecutor(new SqlConnection("ConnectionString")))
        {
            // 列名とプロパティ名を対比させてマッピング
            // パラメータはstring.Format的に@p0, @p1などに対して適用される
            var persons = executor.ExecuteQuery<Person>(@"
select
    FirstName + ' ' + LastName as Name,
    Age
from PersonTable
where Age < @p0 and FirstName = @p1"
                , 20, "Osakana").ToList(); // 遅延評価なのでusingを抜ける前にリスト化などどうぞ

            // ExecuteReadはIEnumerable<IDataRecord>で一行ずつ取得できる
            var persons2 = executor.ExecuteRead(@"select * from PersonTable")
                .Select(dr => new
                {
                    Age = dr.GetInt32(0),
                    FirstName = dr.GetString(1),
                    LastName = dr.GetString(2)
                });
        }
    }
}

といった感じに、処理用のラッパーにDB接続を渡して実行します。メソッドはExecuteScalar, ExecuteNonQuery, ExecuteRead, ExecuteQueryの4つ。ScalarとNonQueryは普通のと同じ、Readは一行毎に列挙、Queryはオブジェクトへのマッピングが出来ます。4つのメソッドの引数は全て同じで、string query, params object[] parametersになります。パラメータは@p0, @p1, といったように「@p順番」に対して適用されます。

実行して即座にコネクションを閉じたい場合は静的メソッドを、接続を維持したまま複数実行した場合はインスタンスを生成してください。DbExecutor自体がIDisposableで、Dispose時に中のコネクションに対しDisposeを呼びます。なお、トランザクション関連のメソッドはありませんが、それはTransactionScopeを使ってくださいな。

スコープと遅延評価

同じ処理はメソッドに括り出す!Don't Repeat Yourself!コピペ禁止!ではあるものの、スコープが絡むと結構難しい。usingは難敵です。以下、実装の一部。

// 実装(一部抜粋)
public class DbExecutor : IDisposable
{
    // usingを共通化させたいんだけど、普通に値返すとスコープ抜けちゃう
    // →IEnumerableで包んでしまえばいいぢゃない!
    private IEnumerable<DbCommand> UsingCommand(string query, object[] parameters)
    {
        using (var cmd = dbConnection.CreateCommand())
        {
            if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
            cmd.CommandText = query;
            foreach (var p in parameters.Select((v, i) => CreateParameter(cmd, "@p" + i, v)))
            {
                cmd.Parameters.Add(p);
            }
            yield return cmd;
        }
    }

    // UsingCommand().First().ExecuteScalar()だとusingを抜けてから実行になるのでダメなのですよー
    public T ExecuteScalar<T>(string query, params object[] parameters)
    {
        return UsingCommand(query, parameters).Select(c => (T)c.ExecuteScalar()).First();
    }

    public IEnumerable<IDataRecord> ExecuteRead(string query, params object[] parameters)
    {
        return UsingCommand(query, parameters).SelectMany(c => c.EnumerateAll());
    }
}

public static class IDbCommandExtensions
{
    public static IEnumerable<IDataRecord> EnumerateAll(this IDbCommand command)
    {
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read()) yield return reader;
        }
    }
}

UsingCommandメソッドが苦心の跡です。ExecuteScalarとExecuteReadの処理(DbCommand取ってパラメータ足す)を共通化したかったのですが、usingが難敵で。ExecuteScalarはTを返すから即時実行、これをusingで一部括り出すのは簡単なのですが、問題はExecuteRead。IEnumerableを返すから遅延実行で、これに対してusing(cmd){return cmd}なんて関数を使ってしまうと、実行時に即座にusingのスコープを抜けてしまってusingが無効になってしまう(どころかDispose済みになってしまうので実効時エラー)。

じゃあどうすればいいか、というと、usingに括り出した部分も遅延評価してしまえばいい。そこで要素一つのみでyield returnする。そして、即時評価のものはSelect->First、遅延評価のものはSelectManyを使うことで、無事Usingを共通化出来ました。おお、Linqは何と素晴らしいのでしょうか!何かと言うと、つまりは、Linq to Objectsの用途はリスト処理だけじゃないんですね。IEnumerableはインフラ。そして、Reactive Extensionsに入っているEnumerableEx.Return(これはEnumerable.Repeat(elem,1)に等しい)の意味合いがジワジワくる。Returnは明らかにHaskell由来の命名で、モナドが(以下略、もしくはナンダッテー)

で、まあ、利用時は結局データベースとの接続やトランザクションとの兼ね合いもあるので、接続の状態自体は割と意識してないとダメですね。適当なところでToListとでもしておいてください。この辺も含有した上での解決策は課題ですね、今はうまいやり方が全然思いつかない。

SQLiteで使う

勿論、SqlServer以外でも使えます。System.Data.SQLiteで試しましたが、問題なく動きました。というわけで、Hello, SQLite。SQLiteをインストールしたら参照設定にSystem.Data.SQLiteを加えて

var builder = new SQLiteConnectionStringBuilder { DataSource = "test.db" };

using (var executor = new DbExecutor(new SQLiteConnection(builder.ConnectionString)))
{
    var existsTable = executor.ExecuteRead(@"select * from sqlite_master where type='table' and name = @p0", "test")
        .Any();
    if (!existsTable)
    {
        executor.ExecuteNonQuery(@"create table test (Age, Name)");
        executor.ExecuteNonQuery(@"insert into test values(10,'hoge')");
        executor.ExecuteNonQuery(@"insert into test values(20,'tako')");
        executor.ExecuteNonQuery(@"insert into test values(30,'ika')");
    }

    executor.ExecuteRead(@"select * from test where Age >= @p0", 20)
        .Select(dr => new { Age = dr.GetInt32(0), Name = dr.GetString(1) })
        .ToList()
        .ForEach(a => Console.WriteLine(a.Name + ":" + a.Age));
}

existsTableのクエリはテーブルがあるかないかを調べるもので、一行帰ってくるならテーブルが存在する、何も帰ってこない時はテーブルが存在しない。というわけで、それAny()で、ですね。IEnumerableベースで扱えると、こういうことが非常に楽です。

それにしてもSystem.Data.SQLiteいいですね、初めて使ったんですが拍子抜けするぐらいに簡単に試せました。インストールしたら参照設定に加えるだけ、DataSourceに直にファイル名指定すれば、あれば読み込み、なければ生成してくれる。データベースは設定が面倒っちいですからねー、こう簡単に出来るのは嬉しいです。

おまけ

で、まあ、VS2008ならLinq to SQL/Entities使わないのー?って話であり、まあ、ねえ、確かに、ねえ。なので、ひっそりとVS2005バージョンも作ってみました(zipに同梱してあります)。内部的にも(当然)Linq未使用なので 、VS2008で対象フレームワークが.NET 2.0の場合でも、こちらなら使えます。内部でSelectとかSelectManyを再定義して、拡張メソッドの呼出じゃなくて普通の呼び出しに書き換えただけです。あまりの型推論の効かなさにイライラしました。もうC#2.0に戻るとか無理すぎるだろ常識的に考えて。

List<Person> persons = executor.ExecuteRead<Person>(@"select * from PersonTable", null,
    delegate(IDataRecord dr)
    {
        Person p = new Person();
        p.Age = dr.GetInt32(0);
        p.Name = dr.GetString(1) + " " + dr.GetString(2);
        return p;
    });

ExecuteReadとExecuteQueryは、IEnumerableを返されても困ると思うので、Listを返すようにしています。ExecuteReadはSelectがないので、第三引数でConverterデリゲートを受けるようにして、Selectの代わりにしました。 使う分には、VS2008のものとさして変わらないと思います。

追記

初回リリース時の名前はDbExecuterだったんですが、DbExecutorに変更しました。stableとか言っておきながら4時間で撤回とか、殺されていいですね、ほんとすみませんすみません。あまりの恥ずかしさに穴掘って埋まりたいです……。まあ、executerでもよくね?と思わなくもなくもないのですが、実際問題ぐぐる先生の検索結果で大きな差があるので、むしろ変えるなら、たった4時間の今のうちしかない、と思ったので変更しちゃいました。しかし、ああ……。スペルは結構気をつけてるほうだと思ったんだけどなあー。

C#で原始的にSQLを扱うお話

世の中はLinq to SQLだのLinq to Entitiesだのを羨ましいなあ、と指を加えて眺めている昨今ですがこんばんわ。Linq好きーな私ですがこのサイトではObjectsとXmlしか扱っていないのは、単純に私が触った事ないから、です。あうあう。そんな私ですが、SQLを(大変嫌々ながら)触らなければならなかったりする場合もないわけじゃないのですが、アレですね、思うのはパラメータ。あれにadd。add。するのが大変美しくない。もっと格好良く、一発で決めようぜ。と思って色々悩んだんですが、どうにも上手く行きそうにない。やけくそになってビルダー+コレクション初期化子を考えてみました。

var cmd = new SqlCommand();
cmd.CommandText = @"select * from Foo where Bar > @Hoge and Tako = @Ika";
cmd.Parameters.AddRange(new SqlParameterBuilder
{
    {"@Hoge", "2"}
    {"@Ika", "たこやき"}
}.ToArray());

分かりづらくなってるだけで、普通にadd, addでいいですね。ダメだこりゃ。ボツ。ちなみに実装は超単純。

public class SqlParameterBuilder : IEnumerable<SqlParameter>
{
    List<SqlParameter> parameters = new List<SqlParameter>();

    public SqlParameterBuilder Add(string parameterName, object value)
    {
        parameters.Add(new SqlParameter(parameterName, value));
        return this;
    }
    
    public IEnumerator<SqlParameter> GetEnumerator()
    {
        return parameters.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

コレクション初期化子の復習をしますと、IEnumerableかつAddメソッドが実装されているクラスに対してコレクション初期化子が使えます。Addは名前で決め打ちされています。メソッド名はAddじゃなくてAppendがいいなー、とか思ってもダメです。コレクション初期化子を使いたい場合はAddです。何でこんなヘンテコなことになってるのか、の理由は2008-02-08 - 当面C#と.NETな記録の記事を参照に。Addが複数引数を取る場合は{{},{}}って書けますが、IntelliSenseの補助がないので割と不便だったりしますねえ。

関数渡し

whileでEndまで読んで、ってのは嫌いです。Stream系のは全部yield returnでライン毎に返す拡張メソッドを定義しますね、私は。というわけで、SQLのDataReaderもStreamと同じ図式なので、同じ感じにしましょー。

var command = new SqlCommand();
command.CommandText = @"select hogehogehoge";
var result = command.EnumerateAll(dr => new
{
    AA = dr.GetString(0),
    BB = dr.GetInt32(1)
});

いい感じに見えないでしょうかどうでしょうか?といっても、これは以前書いたものの使い回しだったりしますが、その以前書いたコードとやらは若干反省してます。以下、書き直したコード。

public static IEnumerable<T> EnumerateAll<T>(this IDbCommand command, Func<IDataReader, T> selector)
{
    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            yield return selector(reader);
        }    
    }
}

public static T[] ReadAll<T>(this IDbCommand command, Func<IDataReader, T> selector)
{
    return command.EnumerateAll(selector).ToArray();
}

そう、以前は無理やりEnumerable.Repeatで無限リピートさせておりましたが、あのテクニックはyield returnが使えない(別関数に分けない場合)時のためのテクニックであって、yield returnが使えるなら素直にwhileループを回して書いた方がマシなのです。どうにも、まずLinqで書いたらどうなるのか、というのが頭に最初に浮かんでしまってフツーの書き方を忘れてしまいがちなのですが、大事なのはシンプルに表現すること、です。何故Linqを使うのか、そのほうがシンプルに書けるから。whileのほうがシンプルになるなら、そちらを選ぼう。弁解のために言っておくと、あれはreturn IEnumerableとyield returnの挙動の違いの例のために書いたわけですががが。あと、yieldが言語によってサポートされてるからってのもありますね。C#1.0のように自前でEnumerator用意して書かなきゃならないのならば、Linqで生成したほうが良いわけで。

マッピング

GetInt32(1)とか列を意識してオブジェクトに詰めるのがダルい。定形作業だし列がズれたら修正面倒だし殺したい。こんなの人間のやる作業じゃない。つーわけで、入れ物に詰めるのぐらいは自動化しよう。ああ、Linq to Sql使いたいなあ。

// このクラスにデータベースから値を入れるとして
class MyClass
{
    public int IntProp { get; set; }
    public string StrProp { get; set; }
}

static void Main(string[] args)
{
    // プロパティ名と対比させて自動詰め込み
    var command = new SqlCommand();
    command.CommandText = @"
            select
                hoge as IntProp,
                huga as StrProp
            from NantokaTable";

    var result = command.Map<MyClass>();
}

public static T[] Map<T>(this IDbCommand command) where T : new()
{
    Dictionary<string, PropertyInfo> properties = typeof(T)
        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
        .ToDictionary(pi => pi.Name);

    return command.ReadAll(dr =>
    {
        var result = new T();
        for (int i = 0; i < dr.FieldCount; i++)
        {
            properties[dr.GetName(i)].SetValue(result, dr[i], null);
        }
        return result;
    });
}

列名とプロパティ名を摺り合わせているだけで、非常に単純なものです。気になるreaderのFieldCountとかGetNameの処理効率ですが、実行した時点で一行のキャッシュが生成されて、そこから取ってくる感じになるので重たくはないっぽいです。たぶん。ちゃんと追っかけたわけじゃないので全然断言は出来ませんが。あとは辞書生成のコストですかね。Type毎で固定なら毎回辞書作らなくてもキャッシュ出来るじゃん、という。静的コンストラクタを使えば、実現できます。

public static class Extensions
{
    public static T[] Map<T>(this IDbCommand command) where T : new()
    {
        return SqlMapper<T>.Map(command);
    }

    private static class SqlMapper<T> where T : new()
    {
        static readonly Dictionary<string, PropertyInfo> properties;

        static SqlMapper()
        {
            properties = typeof(T)
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .ToDictionary(pi => pi.Name);
        }

        public static T[] Map(IDbCommand command)
        {
            return command.ReadAll(dr =>
            {
                var result = new T();
                for (int i = 0; i < dr.FieldCount; i++)
                {
                    if (dr.IsDBNull(i)) continue;
                    properties[dr.GetName(i)].SetValue(result, dr[i], null);
                }
                return result;
            });
        }
    }
}

割とスマート。ジェネリック静的クラスには直接拡張メソッドは定義出来ないので、入れ子のprivateな静的クラスを挟んでいます。 と、こんな感じにSQLを触っていると非常に原始人っぽい。先日のテンプレート置換と同じく、コピペに優しい小粒でピリッと役立ち、なメソッドの作成を志してる感じです。まあ実際は自分だけが使うUtilなんて許されるわけもなく普通に地味にドロドロと(ああ、胃が……)。

そういえばであまり関係ないのですが、SQL文ってどこに置くべきなんでしょうかね。外部ファイルにしておいて読み込み、などというのは個人的にはどうかなー、と思っていて。どうせ呼び出し部分と1:1になるなら、↑のようにハードコードでも別によくないかしらん、そのほうがソースコード上の距離が近いこともあって分かりやすくなる。とか、どうなんでしょうかねえ。再コンパイルが不要になるといって、どうせSQLに変更入ったらコードのほうも変更入れないとマズい可能性が高そう、とか。

追記

マッパーはSystem.Data.LinqにあるDataContext.ExecuteQuery(TResult) メソッド そのものですね、たはは、シラナカッタヨ。↑のほうが超単純実装+キャッシュなので速いとは思いますが、どうでもいい差ですな。全く同じものだと悔しいので、outer joinとかでNullが混じる場合もすんなり使えるように、IsDBNullを足してスキップするようにしました。まあSQLでcoalesceで明示的にやったほうが良いとは思いますが。EnumerateAllのほうは普通に使えると思うのでぜひぜひ。

RxJS用IntelliSense生成プログラム(と、VisualStudioのJavaScript用vsdocの書き方)

先日、Reactive Extensions for JavaScript(RxJS)の記事を書いたわけですが、触っていて困るのは、どのメソッドが使えるの?ということ。リファレンスもない中で、C#版の記憶を頼りに手打ちでメソッド名を探るなんて、無理。ましてやそんな状況じゃあ人に薦められないよ!というわけで、必要なのはIntelliSense(入力補完)です。rx.jsはある。rx-vsdoc.jsはない。ないものは、作ればいいぢゃない。そこで諦めてメソッド全部暗記してやるぜ、とか思うのはどうかしてる。諦めたら試合終了ですよ。楽するために手間を掛けるのです<プログラマの三大美徳。というわけで、作りました。

vsdocファイルをそのまま配布するのはライセンスの問題が出そうなので、生成プログラムを配布します。手作業じゃなく自動生成で作ったので(面倒くさくて手作業なんてやってられるか!)。Rxをインストールしたフォルダ(デフォルトだとProgramFiles\Microsoft Reactive Extensions)のScriptSharpフォルダの下のRxJS.dllとRxJS.xmlを、生成プログラムと同じ階層に置いて実行すると、rx-vsdoc.jsが生成されます。

利用するにはvsdoc対応パッチをあてたVisualStudio 2008 SP1(VS2010はパッチをあてなくても対応しています)を用意して、rx.jsと同じ階層に置くだけです。HTMLで使う場合はscript src="rx.js"で読み込むだけ、独立したjsファイルで補完を使う場合は、行頭に/// <reference path="rx.js" />と記述すれば補完が読み込まれます。この辺は、以前に最もタメになる「初心者用言語」はVisualStudio(言語?)という記事を書いたときに補完愛してる愛してる愛してると連呼しながら解説してました。jQueryのドットで補完効かせながらのメソッドチェーンは気持ちイイんだって!

折角作ったので海外の人にも利用してもらおうと、また、標準でvsdocも同梱して欲しいと訴えるためにもとRxの公式フォーラムでスレ立てたけど、奇怪英語(機械翻訳英語)が恥ずかしいです……。ニュアンスをミジンコほどにも伝えられた気がしません。英語読めない書けないプログラマなんて小学生までだよねー、とかいう自己啓発系ブログ記事は山のようにあるわけですが、ふん、どうせ英語読めませんよ書けませんよ、ぐぐる先生による機械翻訳さえ超進化してくれれば小学生でも生きていけるもん!(ちなみに私はヤフー翻訳派です)

MIX10の発表によるとMicrosoftはAjax関連はjQueryに一本化する、ということで、C#erもますますJavaScriptを書かなければならないシーンは増えていきそうなので、せっかくなのでJavaScript用のvsdocの書き方を解説します。ついでに、LinqまみれなRxVSDocGeneratorのコードの解説も若干します。Mono.Cecil.dll使ってたりするんですよー(モジュールの参照用にしか使っていないので些かオーバースペック)。

C#と比較するJavaScriptの構造

JavaScriptは割とヒネクレた書き方が幾らでも出来るわけですが、VisualStudioの入力補完は、素の状態だと素直に書かないとついてきてくれません。というわけで素直に書きましょう。素直に書けば素直なIntelliSenseが手に入ります。以下、10秒でわかるC#とJavaScriptとの構造比較。

// 名前空間、もしくは静的クラス
Rx = {}
Rx.Disposable = {}
// クラス(コンストラクタ)
Rx.Observable = function(){ }
// 継承
Rx.AsyncSubject.prototype = new Rx.Observable;
// 静的フィールド
Rx.Disposable.Empty = null;
// インスタンスフィールド
Rx.GroupedObservable.prototype.Key = null;
// 静的メソッド
Rx.Observable.Range = function(start, count, scheduler){ }
// インスタンスメソッド
Rx.Observable.prototype.Select = function(selector){ }

ヒネクレたことさえしなければ、JavaScriptはシンプルです。オブジェクトとファンクションしか存在しない。シンプルさ故の制限を回避するために、また、幾らでも回避可能なためバッドノウハウのようなヒネクレた手段が大量に溢れていて、シンプルさとは無縁の奇怪な代物と成り果てていますが(JSはシンプルだよ、初心者にお薦め!というそばからクロージャがどうのapplyがどうのと言うのはどうなのよ、勿論、その柔軟さもまたJSの魅力の一つだとは思いますが、それをシンプルとは言わない)、素直に見れば、シンプルです。

そしてまあ、C#と割と似てます。構文似てるし。単一継承だし。prototypeに後からメソッドを足せるのは拡張メソッドのよう。違いは、privateはないしプロパティはないしインターフェイスはないしオーバーロードもない(但し引数は省略可能)、いつでも簡単に全てが変更可能(不注意に扱えばすぐ構造をぶっ壊せる←だからライブラリの衝突の問題がある)。といった問題は、若干ヒネクレればある程度は回避可能です、privateとか。でも、素直に書いた方が良いと思います。JavaScriptにprivateはない。と、割り切ってしまうと非常に楽になれます。良いか悪いかはともかく。

ただ、素直に書こうと、素のJavaScriptでは、補完は簡単に限界がきます。例えば以下のコード。

var func = function(bool) {
    return (bool) ? "string" : [4, 5, 2, 3, 1];
}

var b = Math.random() < 0.5; // true or false
func(b).toUpperCase();
func(b).sort(); // どちらかで必ずエラー

引数の型が自由なら、戻り値の型もまた自由。じゃあどうするの?というと、どうにもなりません。戻り値の型はなるべく統一しましょう、IntelliSenseに優しくするために。これもまた素直の一つでしょうか、さてはて。

vsdoc.js入門

素直に素直に、と言ったところで何処かで破綻する。だいたい、JavaScriptの言語としての柔軟さを生かさないでどうする!という話は尤もなこと。そこで、VisualStudioはJavaScriptの入力補完に気の利いた仕組みを用意しています。ファイル名-vsdoc.jsが同階層にある場合、vsdoc.jsの構造を利用して入力補完を行います。なので、オリジナルに手を加える事なく補完を利用することが出来ますし、また、オリジナルが補完生成し辛い構造をしていても問題はありません。最終的にユーザーが利用するPublicの構造というのは、上で書いた素直なJavaScriptで再現出来るわけなので、それで構築すればいいだけです。勿論、別箇に構造を作成するというのは手間が増えるので、可能な限りは素直な構造にしておいたほうが無難です。

「IntelliSenseに候補が出ないものは存在しないに等しい」。これは.NETのクラスライブラリ設計という本に書かれている言葉なのですが(神本なので未読の人は絶対購入しましょう)、候補を出しさえしなければ、利用者にとって存在しないようなものに見えます。実際はpublicであっても、補完候補から削ってしまえばprivateに見える。擬似的なprivateの表現としては、中々スマートではないですか?

そんなvsdocですが、ちゃんとしたドキュメントが今ひとつ見あたらないので、jQuery用のvsdocを参考にすると良いでしょう。色々な属性が用意されているようですが、実際の入力補完に利用されるものは少ししかありません。optional属性なんて、オーバーロード的なものの表現に使えるのでは?と期待をかけたのですがそんなことはなくて、IntelliSense用には動作しませんでした。よって、summary, param, returnsだけ抑えておけば良いです。

var sum = function(x, y) {
    /// <summary>足し算</summary>
    /// <param type='Number' name='x'>引数1</param>
    /// <param type='Number' name='y'>引数2</param>
    /// <returns type='Number'></returns>
}

Rx.Disposable.Empty = new IDisposable;

C#と違ってfunctionの「下」にドキュメントコメントを書きます。また、ドキュメントコメントを使う場合は、関数本体はあってもなくても無視されるので、不要です。なお、ドキュメントコメントは-vsdoc.jsだけで有効なわけではなく、普通のjsファイルでも有効です。summary, paramは面倒くさかったら書かなくてもそんなに害はなさそうですが(但し引数違いのオーバーロードがある場合はsummaryで伝えてあげると使う人に優しい)、returns typeだけは欠かさず書いておきたい。これを書いておくと戻り値の型がVisualStudioに認識されるので、IntelliSenseを途絶さず利用できます。

制限事項としては関数のみにドキュメントコメントを埋め込むことが出来ます。vsdocを作る際にフィールドの型も認識させたい場合は、ダミーの変数を与えてあげればOK。

ジェネレータの解説

と、いった基本を抑えておけば、どんなライブラリに対してもvsdocを作れるね!じゃあ、rx-vsdoc.jsも手作業で作ろうか。と、思った時もありました。構造自体はjs自体をダンプでなんとかなる(と、いいなあ)だろうし、summaryやparamは諦めるとしてreturns typeだけを手作業で書くなら、どうせほとんどRx.Observableなので手間もそんなでもない。けど、rx.jsは難読化されていて引数の名前がイミフ、例えばRx.Observable.Range(k0, l0, m0)というんじゃ苦しい……。やっぱsummaryもparamも必要。でもどうすれば……?

そこで、インストールディレクトリを見てみるとScriptSharpなんてフォルダがあるんですよ。そう、RxJSはScript#でC#コードから生成されたJavaScriptライブラリだったのだよ、ナンダッテー!そして、ScriptSharp用のRxJS.dllには当然、完全なクラス構造と、引数の名前と型が保存されているし、更にはsummary用のxmlも用意されていた。つまり、ここからrx-vsdoc.jsを生成すればいいわけです。

というわけでリフレクション。型情報を取るため、早速Assembly.LoadFrom("RxJS.dll").GetTypes()とすると、落ちる。はあ、ScriptSharpのdllに依存してるのでそっちもないとダメなのね。というわけでScriptSharpのdllを幾つか参照に加えると、なんかうまく動かせない。ScriptSharpのdllはmscorlibの代替となってる(JSに変換可能なもののみに制限を加えてる?)から、一緒には動かせないとかそんな感じなのかなー、よくわからないけどとにかく動かせない、諦める。南無。無念。

そもそもLoadするからダメなわけで、Loadしなくていいよ、型情報だけ取れればそれでいいんだって。でも標準ライブラリには、それを可能にするのはないっぽい。けど、Monoにはあった。Cecil - Mono。参照も書き換えも出来るようですが、今回は参照のみで。色々出来そうなので、いつかもう少し触ってみたいですね。私は今回はじめてMono.Cecil.dllを使ったのですが、リファレンスの類も見てない(あるのか知らない)し、チュートリアルの類も見てない(ていうか日本語の情報がない)。でも、IntelliSenseでドット打ってれば何とかなりました。しっかりした構造とちゃんとしたメソッド名とIntelliSenseがあれば、リファレンスがなくても問題なく使えるわけです。すばらしきこのせかい!

Mono.Cecil

型情報を取ってくるだけなら簡単で、というかSystem.Reflectionと大して変わりません。

var rxjsTypes = AssemblyFactory.GetAssembly("RxJS.dll")
    .MainModule.Types.Cast<TypeDefinition>()

TypeDefinition, MethodDefinition, ParameterDefinitionといったのが個の要素。そして、対応するコレクションHogeCollectionが用意されています。HogeCollectionは残念ながらジェネリックではないため、Linqに流すためにはCastが必要になります。今回はParameterDefinitionCollectionのSelectを多用することが多かったので、Cast無しで使えるよう拡張メソッドを定義しちゃいました。

static IEnumerable<T> Select<T>(this ParameterDefinitionCollection source, Func<ParameterDefinition, T> selector)
{
    return source.Cast<ParameterDefinition>().Select(selector);
}

この手のレガシーなコレクションに対するアドホックな対応は、例えば正規表現のMatchCollectionなんかにも使えそうです(と、いった発想の元ネタはAchiralから)

テンプレート置換

必要なJSの構造は上のほうで書いた通り決まったパターンがあるので、雛形を元に置換するのが楽。テンプレートエンジン、なんていう大仰なものは必要ないけれど、string.Formatでも{5}とか出てくると引数の管理が面倒だし、順番の変更にも弱い。なので簡易置換用の拡張メソッドを用意してみました。

static string TemplateReplace(this string template, object replacement)
{
    var dict = replacement.GetType().GetProperties()
        .ToDictionary(pi => pi.Name, pi => pi.GetValue(replacement, null).ToString());

    return Regex.Replace(template,
        "{(" + string.Join("|", dict.Select(kvp => Regex.Escape(kvp.Key)).ToArray()) + ")}",
        m => dict[m.Groups[1].Value]);
}

オブジェクトを渡すと、{プロパティ名}の部分をプロパティの値に置換します。オブジェクトなのでクラスインスタンスでもいいのですが、匿名型も使えます。例えば

const string classTemplate = @"
{FullName} = function({Parameters})
{
    /// <summary>{Summary}</summary>
{Param}
}";

var r = classTemplate.TemplateReplace(new
{
    FullName = "Rx.Notification",
    Parameters = "kind",
    Summary = "Represents a notification to an observer.",
    Param = "    /// <param type='String' name='kind'></param>"
});

Console.WriteLine(r);

割と便利。たった9行なので、ちょっと気の利いた置換が欲しいなあ、って時にササッとコピペして取り出せるのが魅力です。最近、コピペに優しいプログラミングをよく考えてる。というのはともかくとして、実際どんな風に使っているかというと、

var classes = rxjsTypes
    .Where(t => t.Constructors.Count > 0)
    .Select(t => new
    {
        t.FullName,
        Parameters = t.Constructors.Cast<MethodDefinition>()
            .Select(m => m.Parameters)
            .MaxBy(p => p.Count)
            .Select(p => p.Name)
            .ToJoinedString(", "),
        Summary = summaries[t.FullName] + " " + t.Constructors.Cast<MethodDefinition>()
            .OrderBy(m => m.Parameters.Count)
            .Select(m => m.Parameters.Select(p => p.Name).ToJoinedString(", "))
            .Select((s, i) => string.Format("{0}:({1})", i + 1, s))
            .ToJoinedString(", "),
        Param = t.Constructors.Cast<MethodDefinition>()
            .MaxBy(m => m.Parameters.Count)
            .Parameters
            .Select(p => string.Format(Template.Param, p.ParameterType.ToJSName(), p.Name))
            .ToJoinedString(Environment.NewLine),
    })
    .Select(a => Template.Class.TemplateReplace(a));

前段階で匿名型を生成して、最後のSelectで置換をかけてます。Select二段にしないでもいんじゃね?というとYESですが、このほうが見やすいと思うので。それにしてもリフレクションなわけで、Linqと非常に相性が良い。というか、Linqなしだと大量のforループとifで涙を流すことになりそう。なので、昔はリフレクションって結構敷居が高かったのですが、今はもうLinqでサクサクとWhereで切って捨ててSelectで繋げて繋げて、って出来るので書く分には楽チンです。これがLinq以前のC#2.0だったら、考えたくないなあ。

出力

クラス、継承、オブジェクト、メソッド、プロパティは全部バラバラに抽出しています。そして、全部IEnumerable<string>で止めています。最後にそれらをまとめて、テキストとして出力。

var vsdoc = Enumerable.Repeat(string.Format(Template.Object, RootNamespace), 1)
    .Concat(classes)
    .Concat(inheritance)
    .Concat(objects)
    .Concat(methods)
    .Concat(properties)
    .ToJoinedString(Environment.NewLine);

File.WriteAllText("rx-vsdoc.js", vsdoc, Encoding.UTF8);

せっかくクエリ遅延評価にさせているので、書き出しも一度stringに貯めないでストリームで書きだせば高効率ですねー。でも、一度文字列に出した方が書くの楽なので。せいぜい1000行程度なので、ケチッても意味ないですな。

全部rxjsTypesをルートにして生成しているので、クエリ構文を使って巨大な一塊にしてみたら面白かったかな、なんて思いますが若干悪趣味な気もするのでやめておきます。そもそも、このドットだらけ、Selectだらけの時点で若干どうよ、といった趣が漂っているのは間違いない。いやいや、ドット素敵です。Linq素敵なんだって、本当に。こういうの書いてるとC#2.0と3.0は別物だろ常識的に考えて、と思わなくもない。セミコロン率は物凄く低くなりましたね……。あと、LinqとSQLを関連付けるのはそろそろやめようぜー、的な思いがふと過ぎったり。Twitterのpublic検索でlinqをキーワードに毎日眺めてるんですが、今でも割とそういう印象持ってる人多いんだなー、と。だからどうしたとかどうなるってこともないですが。

まとめ

IntelliSenseでLinqはより楽しくなる。メソッドチェインはIntelliSenseでより楽しくなる。そして、VisualStudioはJavaScriptエディタとしても優秀なので皆VisualStudio使おう!インストールが面倒?Microsoft Visual Studio 2008 Express EditionからWeb インストールをクリックするだけでオールインワンでダウンロード含めて10分ぐらいで全部やってくれる。時間がかかるというのは正しいですが、意外と面倒くさくはないんです。それと、このExpress Editionは無料です。

入力補完だけじゃなく、コード整形やデバッガ(開発環境と完全統合されているためFirebugよりもずっと使いやすい)などもあるし、ある程度は裏でインタプリタをぶん回して変数名間違いなどのエラーを補足してくれるので、IDE無しでJavaScript書くなんて、そんな苦労、しなくてもいいんだよ……。

Reactive Extensions for JavaScript

MIX10終了しましたねー。何はともかく皆してIE9の話題ばかりで、ああ、InternetExplorerは愛されてるなあ(色々な意味で)、などというのを横目に、私にとっての最大のニュースはReactive Extensions for JavaScript(RxJS)です。Reactive Extensions(Rx)はこのサイトでもカテゴリーを作ってメソッド探訪なんてやってるぐらいに注目していたわけで、当然MIX10でJavaScript版出すという話を聞いた時からwktkが止まらなかったわけですが、全く期待を裏切らなかった!というか、こいつはヤバいですよ?

Reactive Extensionsとは何ぞや、というと、LinqというC#の関数型言語みたいなリスト処理ライブラリ(語弊ありまくり)のイベントとか非同期版です。イベントや非同期処理にたいしてmapとかfilterとかfoldとか、お馴染みなリスト操作関数が使えちゃうという、その発想はなかったわ、なライブラリです。C#版は去年の夏ぐらいにプレビュー版が出て、いまも精力的に開発が続いているのですが、今回はJavaScript移植版が出た、という話です。イベント(onclick!onclick!)や非同期(XMLHttpRequest!)ってのは、勿論C#でも大事なのですが、JavaScriptなんてそれが主役というぐらいなわけなので、C#版よりもインパクトは大きいです。何よりも、C#は言語やライブラリが強力なので別にRx使わなくてもって感じなのですが、JavaScriptは違う。あまりにも貧弱。なので、優れたイベント/非同期処理ライブラリの重要度はC#の比ではなく高い。

RxJSの面白いところはjQueryに対抗するものではなく、むしろ協調動作するように作られていることです。DOM操作はjQuery、イベントや非同期処理はRxJS、二つの強力なライブラリを組み合わせることで、JavaScriptプログラミングは次のパラダイムへ向かおうとしています。御託は、もういいですね、とりあえずサンプルを。

マウスをクリックして四角形を掴んで、マウスを動かして、放すという、ごく普通のドラッグアンドドロップ。素の JavaScriptではどうやって実装しますか?グローバルに状態管理用のオブジェクトを置いて、オフセット用の変数置いて、マウスの状態を監視して、うーん、考えたくない。あまり綺麗に出来そうにない。それがRxJSを使ってみると――

var O = Rx.Observable; // using

$(function()
{
    var doc = $(document);
    var area = $("#dragArea");

    O.FromJQueryEvent(area, "mousedown")
        .Select(function(e)
        {
            var offset = $(e.target).offset();
            return { X: e.pageX - offset.left, Y: e.pageY - offset.top }
        })
        .SelectMany(function(offset)
        {
            return O.FromJQueryEvent(doc, "mousemove")
                .TakeUntil(O.FromJQueryEvent(doc, "mouseup"))
                .Select(function(e) { return { X: e.pageX - offset.X, Y: e.pageY - offset.Y }; })
        })
        .Subscribe(function(a) { area.css({ left: a.X, top: a.Y }) });
});

状態管理変数は使いませんし、全て一連のメソッドチェーンだけで完結します。以下解説。

Rx.Observable.FromJQueryEventは、イベントをRxオブジェクトに変換します。これはjQueryの$などと同じものだと思ってください。FromJQueryEventと同様の動作をするものにFromHtmlEventというものがあります。ほとんど同様ですが、jQueryオブジェクトを使う場合はFromJQueryEvent、getElementByIdなどで得られたネイティブの要素を使う場合はFromHtmlEventを使いましょう。基本的には、クロスブラウザ回りの面倒事を任せられるjQueryとの併用がお薦めです。

Rxオブジェクトに変換後は、多数のメソッドをjQueryのようにチェーンさせて記述していきます。どれだけ多数かというと、ここに並べてみます。

Subscribe, Select, Let, MergeObservable, Concat, Merge, Catch, OnErrorResumeNext, Zip, CombineLatest, Switch, TakeUntil, SkipUntil, Scan1, Scan, Finally, Do, Where, Take, GroupBy, TakeWhile, SkipWhile, Skip, SelectMany, TimeInterval, RemoveInterval, Timestamp, RemoveTimestamp, Materialize, Dematerialize, AsObservable, Delay, Throttle, Timeout, Sample, Repeat, Retry, BufferWithTime, BufferWithCount, StartWith, DistinctUntilChanged, Publish, Prune, Replay

何も一気に全部を知る必要はないので、あまり圧倒されずに、少しずつ学んでいければいいかな? 私もちょいちょいとブログ記事で紹介していきたいと思っています(やるやる詐欺ばかりですが……)。そうそう、ソースコードが圧縮されていて実際の名称は不明なのでRxオブジェクトと呼んでますが、それであってるかは今のところ謎です。C#ではIObservableなので、IObservableでいいかな、という気はしますが。

イベント as リスト

もう少しFromJQueryEventの動きを考えますか。イベントが発生すると、後ろのメソッドにイベントオブジェクトが渡されます。再度クリックすると、またイベントが発生しイベントオブジェクトが渡されます。再度(以下略)。つまりは、[event, event, event...]。終りのない配列。無限リスト。まるでイテレータのような……。そう、ObserverパターンとIteratorパターンは同じなのだよ、ナンダッテー!よく分からない?確かに。こういう時は他の人の言葉を借りてしまおう。最近Scalaの入門記事が注目を集めました。「神は言われた。「リストあれ。」」。そうです、イベントもまた、Rxの手にかかればリストになってしまうのです。Rxは関数型言語のリスト操作のようにイベントを高階関数で処理できます。それがもたらす世界、想像するとワクワクしませんか?なお、JavaScriptで配列やDOMに対してリスト操作を行うライブラリとしてlinq.jsというがありますのでよろしくお願いします←宣伝(作ってるの私なので)。

ドラッグアンドドロップの構造

mousedownでイベントが発動しても、本当に必要なイベントはdownじゃなくてmove。なので、downはイベント発動とオフセット算出にだけ使って、実際に後ろに流す情報は別のところから得ます。そこで、mousedownとmousemoveを合流させなければなりません。シーケンス(Rxによりリストのような何かになっているので、Sequenceが言葉として適切な気がします)の結合はMergeやCombineLatest、Zipなど、用途に応じて色々あるのですが、ここは一(mousedown)から多(mousemove)の状態を作ることが可能なSelectManyを使用します。SelectManyに渡す高階関数の戻り値(Rxオブジェクト)が、更に平たくされて後ろのメソッドに渡されていくことになります。

TakeUntilは、「~まで取得する」。引数のイベントが発動されるまでシーケンスを流し、発動されたら一切流さなくなる。つまりFrom(mousemove).TakeUntil(mouseup)は、mouseupされるまでmousemoveイベントを発行するということ。驚くほど簡単にドラッグアンドドロップの構造が記述出来てしまいました。これはヤバい。Rxヤバい。簡潔すぎるだろ常識的に考えて。

Selectは多くの関数型言語やRubyなどで言うところのmapで、要素を変形して返すもの。ここではeventからclientX, clientYだけのオブジェクトを作っています。mapがあるということは、勿論filterもあります(メソッド名はWhere)。この辺はlinq.jsの解説がそのまんま適用出来ます。何故かというと、Observerパターン(RxJS)とIteratorパターン(linq.js)は(以下略)。

Subscribe

シーケンスを変形していったら、最後に登録してやる必要があります。それがSubscribe。Subscribeを呼んで、初めてイベント(mousedownなど)に関連付けられます(addEventListenerです、ようするに)。このSubscribeは感覚的にはforeachのようなもので、無名関数の第一引数に今までに変形させた変数が入っているので、それを取り出して何らかのアクションを取る。今回はstyleを弄って四角形の座標を変更してやりました。

addEventListenerということは、デタッチもあるの?というと、ありますあります。Subscribeの戻り値はvoidではなく、IDisposableオブジェクトというものになっています。この戻り値のIDisposableオブジェクトを取っておけば、デタッチさせたい時にDisposeメソッドを呼んでデタッチさせられます。

Rx.Observable

Rxオブジェクトのメソッド一覧は書きましたが、Rx.ObservableにはFromHogeHoge以外にも色々なメソッドがあるよ。イベントだけじゃなく、あらゆる方向からRxオブジェクトを作り出す驚異のメソッド群はこれだ!

Amb, Catch, Concat, Create, CreateWithDisposable, Defer, Empty, FromArray, FromDOMEvent, FromHtmlEvent, FromIEEvent, FromJQueryEvent, Generate, GenerateWithTime, Interval, Merge, Never, OnErrorResumeNext, Range, Repeat, Return, Start, Throw, Timer, ToAsync, Using, XmlHttpRequest

いっぱいありますねー。名前から想像つくものからつかないものまで。XmlHttpRequestとか興味をひくところです。そう、Rxはイベントだけではなく、非同期通信までリストに変換し統一的な操作を可能にしてしまうわけです。こっちも重要なので、後日サンプル書きます。

結論

jQueryあるからRxJSなんてイラね、というわけじゃあないんですよ!ふたり揃ってプリキュア。最初の方にも書きましたが、jQueryのDOM操作は素晴らしいのでおまかせでいいです。イベントや非同期処理はjQueryだけじゃ足りないところがあります。そこはRxJSで補います。ついでに通常のリスト操作(配列やDOM Elements)は全然足りてません。prototype.jsはあんなにイけてたのに、jQueryになってからリスト処理がシンドいですね。そこはlinq.jsです(宣伝しつこい)。これでもうJavaScriptに死角はなくなった……、勝った、HTML5時代バンザイ!(でも私はC# + Silverlight4に期待をかけるけどね)

参考リンク

Rxチームの一員であるJeffrey van Goghによる、Rxをインストールしたディレクトリに置いてある、TimeFilesサンプルの解説。forループがダサいのでlinq.jsを使って書き直してTwitterに流したんですが、そうしたらJeffrey van Goghに言及してもらった!。これは嬉しい。

Matthew PodwysockiによるRxJSの解説シリーズ。From(mousemove).TakeUntil(mouseup)のネタ元はここだったりして。最高にクール!

はてなダイアリー to HTML

はてなダイアリーの記事を根こそぎ取得してローカルHTMLに保存するアプリケーションです。過去ログを全部取得して昇順に並び替えます。カテゴリ指定も可。 本文抽出アルゴリズムだなんて高尚なことはせず、HTMLをそのまま切り出しているだけのはてなダイアリー完全特化なぶんだけ、デザインやsyntax-highlightなどもそのままで見ることができます。上の画像はNyaRuRuの日記(勝手に貼ってすみません)の.NETカテゴリーを抽出しているところ。私がC#やLinqを覚えられたのはNyaRuRuさんの日記のお陰といっても過言ではなく、しかも読み返す度に新しい発見があって本当に素晴らしい。ので、度々読み返しているのですが、はてな重い。重い。なら全部ぶっこぬけばいいぢゃない。というのが作った理由でして……。

あと、最近こそこそごそごそとC++も勉強中なので、 [C++] - Cry’s Diary[C++] - Faith and Brave - C++で遊ぼうを読むと、(大体は全く分からないのですが)勉強になります。なお、Permalinkは相対パスになってしまい使えないのですが、日付の部分は絶対パスなので、コメント見たくなったりPermalinkを取りたくなったら日付から辿れます。

こうしてHTMLを自炊(?)すると、電子ブックリーダー欲しくなりますね。それと、リーダーはやっぱブラウザが載ってないとダメよねー。PDF(と独自形式?)だけ見れても嬉しくぁない。そんなに本には興味ない。HTMLが見たいのです。Twitterのログが見たいのです。2chまとめサイトが見たいのです。海外の技術書は結構PDFで買える感じなのでそれはそれで気になるところですが――。

以下ソースコード。↑のzipにも同梱してありますが。コンパイルにはSGMLReaderが必要です。

static class Program
{
    static IEnumerable<T> Unfold<T>(T seed, Func<T, T> func)
    {
        for (var value = seed; ; value = func(value))
        {
            yield return value;
        }
    }

    const string HatenaUrl = "http://d.hatena.ne.jp";

    static void Main()
    {
        Thread.GetDomain().UnhandledException += (sender, e) =>
        {
            Console.WriteLine(e.ExceptionObject);
            Console.ReadLine();
        };

        Console.WriteLine("抽出対象のはてなIDを入力してください");
        var id = Console.ReadLine();
        Console.WriteLine("カテゴリを入力してください(全ての場合は空白)");
        var word = Console.ReadLine();
        Console.WriteLine("出力ファイル名を入力してください");
        var fileName = Console.ReadLine();

        // 抽出クエリ!
        var root = XElement.Load(new SgmlReader { Href = HatenaUrl + "/" + id + ((word == "") ? "" : "/searchdiary?word=*[" + Uri.EscapeDataString(word) + "]") });
        var contents = Unfold(root,
            x =>
            {
                var prev = x.Element("head").Elements("link")
                    .FirstOrDefault(e => e.Attribute("rel") != null && e.Attribute("rel").Value == "prev");
                if (prev == null) return null;
            retry:
                try
                {
                    var url = HatenaUrl + prev.Attribute("href").Value;
                    Console.WriteLine(url); // こういうの挟むのビミョーではある
                    return XElement.Load(new SgmlReader { Href = url });
                }
                catch (WebException) // タイムアウトするので
                {
                    Console.WriteLine("Timeout at " + DateTime.Now.ToString() + " wait 15 seconds...");
                    Thread.Sleep(TimeSpan.FromSeconds(15)); // とりあえず15秒待つ
                    goto retry; // 何となくGOTO使いたい人
                }
            })
            .TakeWhile(x => x != null)
            .SelectMany(x => x
                .Descendants("div")
                .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "day"))
            .TakeWhile(e => !Regex.IsMatch(e.Value, @"^「\*\[.+\]」に一致する記事はありませんでした。検索語を変えて再度検索してみてください。$")) // 間違ったカテゴリ入力した時対策
            .Reverse(); // 古いのから順に見たいので

        // style抽出
        var styles = root.Element("head").Elements("link")
            .Where(e => e.Attribute("rel").Value == "stylesheet")
            .Select(e => { e.SetAttributeValue("href", HatenaUrl + e.Attribute("href").Value); return e; }) // 副作用ダサい
            .Concat(root.Element("head").Elements("style"));

        // HTML組み立て!
        var html = new XStreamingElement("html", // まあ、Reverseでバッファに貯めるので焼け石に水ですけどね、XStreamingElement
            new XStreamingElement("head", styles),
            new XStreamingElement("body",
            // new XElement("div", new XAttribute("class", "hatena-body"), サイドバーとか邪魔なので無視
            // new XElement("div", new XAttribute("class", "main"),
                new XStreamingElement("div", new XAttribute("id", "days"),
                    contents)));

        // 保存
        var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), fileName + ".html");
        var xws = new XmlWriterSettings { Indent = true, CheckCharacters = false }; // 不正な文字のあるサイトを書き出すと落ちるので防止
        using (var xw = XmlWriter.Create(path, xws))
        {
            html.Save(xw);
        }
    }
}

try-catchが出るとゴチャついて嫌。なのだけど、しょうがないか。それと Unfoldはどうしたものかねえ。極力、標準演算子のみで済ませたいんですが、今回はちょっと使わざるを得なかったと思っています。次のページのURLを得るには、取得したHTMLから解析しなければならない。下流で解析し取得した次のURLは、上流に渡さなきゃいけない。のですが、通常は下から上に渡せないのがLinqなのよね。そんな場合、外部変数を介して渡すか、Unfoldか、場合によってはScanなんかを使うかになるわけで、とにかく外部変数は避けたかったのでUnfoldを使いました。

HTMLへの書き出し部分では物珍しい XStreamingElementを使ってみました。今回はただ単に書き出すだけなので、通常のXElementのようにメモリ内にツリーを保持する必要はないし、相当大きいXmlを扱うため効率も気になってくるところ。そこで遅延ストリーム書き込みを可能にするXStreamingElementの出番です。詳しくは 方法: 大きな XML ドキュメントのストリーミング変換を実行する をどうぞ。とはいっても、このプログラムでは反転させるためReverseでバッファに全て溜め込んでいるので、まあ……。XStreamingElementって言いたいだけちゃうんか、みたいな。

デザインはdiv class=hatena-bodyとdiv class=mainを抜いているので(不必要なサイドバーの描画を除去するため)、この二つに依存するCSSが書かれているサイトの場合はデザインが崩れることがあります。ちなみにneuecc clipはこの二つどころか、その他にもwrapperを置いているというデタラメなCSS構造をしているため、デザインは保存出来ません。全くもって酷い。もっとスクレイピングに優しいHTMLを書かないとダメですな。

HTMLへの書き出し部分ははまりどころでした。最初Save(fileName)で保存していたんですが、特定のサイトの特定の部分で落ちてしまって困りました。具体的には 2008-07-23 - Faith and Brave - C++で遊ぼう で(例に出してすみません)、Protocol Bufferによる出力結果がInvalidXmlCharに引っかかってアウト、のようです。回避する方法は、XmlWriterSettingsのCheckCharactersをfalseに設定したXmlWriterを生成して書き出せばOK。

XboxInfoTwit - ver.2.2.0.0

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

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

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

C#でスクレイピング:HTMLパース(Linq to Html)のためのSGMLReader利用法

Linq to XmlがあるならLinq to Htmlもあればいいのに!と思った皆様こんばんは。まあ、DOMでしょ?ツリーでしょ?XHTMLならそのままXDocument.Loadで行けるよね?XDocument.Parseで行けるよね? ええ、ええ、行けますとも。XHTMLなら、ね、ValidなXHTMLならね。世の中のXHTML詐称の99.99%がそのまま解析出来るわけがなく普通に落ちてくれるので、XDocumentにそのまま流しこむことは出来ないわけです(もちろん、うちのサイトも詐称ですよ!ていうかこのサイトのHTMLは酷すぎるのでそのうち何とかしたい……)。

そこでHtmlを整形してXmlに変換するツールの出番なわけですが、まず名前が上がるのがTidy、の.NET移植であるTidy.NETで、これは論外。とにかく面倒くさい上に、パースしきれてなくてXDocumentに流すと平然と落ちたりする。おまけにXDocumentに入れるには文字列にしてから入れる必要があって二度手間感がある、などなど全くお薦めできません。XboxInfoTwitはTidy使ってますが、後悔してますよ……。

次にHtml Agility Packで、これは中々良いです。Linq to Xml風で大変使いやすい。のですが、あくまで風味であって、なんで本物のLinq to Xmlが目の前にあるのに、それっぽく模したものを覚えなきゃいけないの?二度手間で面倒くさいよ。

そこで、第三の選択としてSGMLReaderを使うという方法を提案します。SGML Reader自体は古くからあるのですが、日本語での情報はあまりないみたいだしLinq to Xmlと組み合わせたスクレイピング用途、に至っては皆無のようなので、ここで紹介しましょう。とりあえず例を。

// たったこれだけのメソッドを用意しておけば
static XDocument ParseHtml(TextReader reader)
{
    using (var sgmlReader = new SgmlReader { DocType = "HTML", CaseFolding = CaseFolding.ToLower })
    {
        sgmlReader.InputStream = reader; // ↑の初期化子にくっつけても構いません
        return XDocument.Load(sgmlReader);
    }
}

static void Main(string[] args)
{
    using (var stream = new WebClient().OpenRead("http://www.bing.com/search?cc=jp&q=linq"))
    using (var sr = new StreamReader(stream, Encoding.UTF8))
    {
        var xml = ParseHtml(sr); // これだけでHtml to Xml完了。あとはLinq to Xmlで操作。

        XNamespace ns = "http://www.w3.org/1999/xhtml";
        foreach (var item in xml.Descendants(ns + "h3"))
        {
            Console.WriteLine(item.Value); // bingでlinqを検索した結果のタイトルを列挙
        }
    }
}

見たとおり、信じられないほど簡単です。SgmlReaderはXmlReaderを継承しているため、XDocument.Load(xmlReader)にそのまま流し込めます。また、SgmlReader自体もDocTypeとInputStreamを設定するだけという超簡単設計になっているため、楽にHtml to Xmlが実現。HtmlはXDocumentになってしまえさえすれば、あとは慣れ親しんだLinq to Xmlの操作で抽出していけます。

例では、BingでLinqを検索した結果の検索結果見出し部分を抽出しています。見出しはh3で囲まれているので、Descendants(ns + "h3")。以上。超簡単。C#のスクレイピングの簡単さはRubyも超えたね!

残る問題は、日本語を扱う際はエンコーディング周りの設定が面倒くさい(間違ったエンコーディングだと文字化けする)、ということなのですが、そのWebClientのエンコーディング問題は、.NET Framework 4.0から修正された System.Net.WebClient は、HTTP ヘッダーから Encoding を自動的に認識してほしい | Microsoft Connect らしいです。素晴らしい!提案して頂いたbiacさんに感謝。

追記(2014/3/26)

いつのバージョンからか、TimeoutでWebExceptionが出るようになってたかもしれません。これはdtdを読みに行こうとしているのが原因のようなので、 IgnoreDtd = true を足してやれば回避できます。

using (var sgmlReader = new SgmlReader { DocType = "HTML", CaseFolding = CaseFolding.ToLower, IgnoreDtd = true })

ということです。

追記(より簡単に)

上の記事を書いてから気づいたのですが、HrefプロパティにURLを指定するだけで、中でStream類を作って自動的にHtmlだと判別してくれるようです。更には、エンコーディングもContentTypeを見て自動調整してくれます(詳しくはSgmlParser.csのOpenメソッドを参照)。よって、もっとずっと簡単に書けます。

static void Main(string[] args)
{
    XDocument xml;
    using (var sgml = new SgmlReader() { Href = "http://www.xbox.com/ja-JP/games/calendar.aspx" })
    {
        xml = XDocument.Load(sgml); // たった3行でHtml to Xml
    }

    // Xboxの発売スケジュールからタイトルと発売日を抜き出してみる
    var ns = xml.Root.Name.Namespace;
    var query = xml.Descendants(ns + "table")
        .Last()
        .Descendants(ns + "tr")
        .Skip(1) // テーブル一行目は項目説明なので飛ばす
        .Select(e => e.Elements(ns + "td").ToList())
        .Select(es => new
        {
            Title = es.First().Value,
            ReleaseDate = es.Last().Value
        });

    // 書き出し
    foreach (var item in query)
    {
        Console.WriteLine(item.Title + " - " + item.ReleaseDate);
    }
}

usingの辺りが若干鬱陶しいので、最初の例のようにメソッドに切り出してもいいかもしれません(CaseFolding.ToLowerも付けたいし)。Loadし終わったらストリームはもう不要です。XDocumentはメモリ内に全部構築するタイプのもので、実質XmlDocument(DOMツリー)の代替となっています。抽出時のXml名前空間ですが、サイトによってついていたりついていなかったりするので、var ns = xml.Root.Name.Namespaceとしておくと、全てのサイトに対応出来ます。

抽出のテクニック

上の例は Xbox.com | Xbox ゲームソフト 発売スケジュールからタイトルと発売日を抽出するというものです。定期的にページを監視して、更新されたらTwitterに投稿するXbox発売予定BOTとか作れますね!Xmlで取れるAPIさえあれば……なんてことはなくなりました!これからは 全てのサイトが易々とスクレイピング可能な代物として浮き上がってきますな。

ただし、元がHTMLのものはAPIとして用意されているXMLと違って、抽出に優しくない構造をしています。 Descendants一発でOk、というわけにもいかないので若干の慣れは必要かもしれません。今回の例の抽出コードが何やってるかよくわからない、という人はHTMLソースと見比べてみてください。目的のTableに辿り着くにも、いくつかの方法があります。決め打ち成分が入ってしまうのはどうにもならないのですが、何を決め打ちにするのがスッキリ書けるのか、となると色々です。今回はTableがLastである、という点を使いましたが、他の方法を考えてみると

var case2 = xml.Descendants(ns + "div")
    .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1")
    .SelectMany(e => e.Descendants(ns + "tr"));

var case3 = xml.Descendants(ns + "table")
    .First(x => x.Ancestors().Any(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1"))
    .Descendants(ns + "tr");

目的のTableを囲むdivのclassが XbcWpFreeForm1であり、XbcWpFreeForm1が適用されているdivは一つしかない、ということに着目するとこうなります。case2は、divを全て列挙して探し出す方法。First(predicate)ではなくWhere.SelectManyにすることで、目的のTableが複数個ある場合でも対応出来ます。case3はTableに絞った上で、そのTableの上位階層(Ancestors)にXbcWpFreeForm1が含まれるかを探し当てる方法。Last、という決め打ちが難しい(場合により変動するケース)場合には有効でしょう。この、上位階層(Ancestors)や下位階層(Descendants)の要素に特異な要素はないか(Any)、と探す手法は、ターゲット自体に特徴がなく抽出し難い場合に活用出来ます。

そもそもDescendantsなんて富豪すぎて許せん、実行効率命!という人はElement("body").Element("div")....とトップから掘っていってもいいわけですが、さすがにElementの連続はダルいのでXPathを使うのもよいでしょう。Linq to XmlでのXPathの利用法は、以前neue cc - そしてXPathに戻るに書きました。XPathは、複雑なことを書こうとすると暗号めいた感じになるから余りすきじゃないですね。私は多少冗長なぐらいでもLinqで書くのが好きだなあ。

何故C#には(Javaの)Collections.sortに相当するものがないのか

Java は Collections.sort があるのに .NET は List 自身が Sort メソッドを持っているのはなぜ?

だそうです。あまり疑問でもないです。だってIListはSortないし。比較するならIList - ListでありList - ArrayListでしょう。とはいっても、JavaのArrayListはほとんどList(Javaのほうの)と同じようなものですが、そうなってくるとむしろメソッドがなくて不便!って思ってしまうのはC#脳。

そして何故かスレッドの話は迷走していて不思議。パフォーマンス? んー……。勝手な印象論ですみませんが、1さん(名前を出すのもアレなので1さん、ということにさせてください)はJavaのCollections.sortの性能を勘違いしているんじゃないかな? 配列のコピーを問題にしてるようだけど、Javaの方式はコピー、してますよ。もっとも最初の方の発言(効率は重要じゃない)と後ろの発言(コピーが嫌)が矛盾していますが。ともかく、実際のSunの実装を見てみましょう。

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

toArrayしてコピー。それをsortして、forで詰め替え。C#で同じような外部Sort関数を書くのならば以下のようになります。厳密には違いますが、それは最後に述べます。

static void Sort<T>(IList<T> list)
{
    T[] array = new T[list.Count];
    list.CopyTo(array, 0);
    Array.Sort(array); // List<T>のSortもArray.Sortを利用しています

    for (int i = 0; i < array.Length; i++)
    {
        list[i] = array[i];
    }
}

forで詰め替えている様は、マジマジと見ると微妙。実際、このコードには問題があります。クイズだと思って、どこが問題なのか考えてみてください、答えは最後に述べます。

List<T>のSortは、内部に配列を持っているため、CopyToとforでの詰め替えが不要です。パフォーマンスで言えば理想的な形になっているわけです。 ゲッタとセッタ経由でソートの入れ替えすればいいぢゃん、というのはそうですね、違います。配列のほうが速いし!というのもそうでしょうが、それ以前にListインターフェイスの実装は自由です。例えばLinkedListのゲッタを考えてみたらどうでしょう。getの度に前(もしくは後ろ)から走査があるため、パフォーマンスが悲惨な事になるのは容易に想像出来ます。ListはArrayListだけじゃないので、ゲッタ/セッタに頼るのは無理がある。汎用的に、Listインターフェイスならば何でも受け入れるという設計にする以上、コピーを作るのは不可避です。

というわけで、パフォーマンス云々を言うならば、内部を知っているListクラスがSortを持つのはベストな選択でしょう。そして利便性を考えてもベストな選択。ならばいったい何処に不満があるのでしょうか?

Javaのほうが優れているのは、「自前でIListを実装したクラス」を破壊的ソートするのに、クラスにSortメソッドを用意したくない。といったところでしょうか。Sortの実装自体は通常はArray.Sortを呼ぶだけなので簡単なので別に手間でもないのですけどね……。というか、この手の基本アルゴリズムを自前実装したのを使うのは悪です(勉強用に、なら当然すべきで悪なのは自前実装の妄信です)。

ようするところ、Sortメソッドを自前で用意したくないけど破壊的ソートが欲しい、ということになる。ふーむ、個人的にはなくてもいいかな。非破壊的なソートがLinqのOrderByを使うことで可能なので、破壊的のほうを欲しいとはあまり思わない。

ListIterator

では本題。何でC#には破壊的ソートをしてくれる外部関数がないの?というと、インターフェイスの都合上、不可能だから。が理由だと私は考えています(そもそも必要性薄いから、が最大の理由だと思いますがそれはそれとして)。Javaのsortと私の書いたC#のSortを見比べてください。大きな違いがあります。それは、forでソートした配列をリストに詰め直している部分。

i.set((T)a[j]); // Java
list[i] = array[i]; // C#

C#の場合、listがLinkedListのようなものだった場合は悲惨なことになります。それに比べて、JavaではListIteratorを用いているため、配列と同じ処理効率で値をセット出来ます。C#には、このJavaのListIteratorに相当するものがないので、全てのIListに対して問題なく性能を発揮する破壊的ソート関数を作成することは不可能です。

「LinkedListのようなもの」という歯切れの悪い言い方をしたのは、.NETのLinkedListはIListを実装していないからです。インデクサで気楽にアクセスすることは出来ません。これは、大変素晴らしい決断だと思います。軽い操作だと勘違いさせるような重たい処理は最初から用意しない。(なお、どうしてもindexで取得したい場合はLinqのElementAtが使えます)。JavaのLinkedListと比較してC#のは貧弱すぎて困るぜ!とか思っていたならば、それは大間違いで、むしろJavaのLinkedListが危険すぎて困る。

ただ、IListのインデクサは取得にコストがかからないことを期待、してもいいとは思います。そんなのを一々気にしてたら何も作れない。それにちゃんと、.NETのLinkedListはIListじゃないしね。ね。というわけで上のほうで出したクイズは、問題があるかないかは何とも言えない微妙ラインです。んーと、つまりはfor(int i;i < hoge.length(); i++)の問題点はどこだー!みたいな話で、基本的にはhoge.length()なんてコストがかからないのを期待して問題ないし、コストがかかるんならそのクソクラスが悪い、みたいな。

ついでに個人的な意見ですがListインターフェイスにListIteratorはそこまで必要ではない。普通のIteratorと機能がかなり被る割には、使う機会はとても少ない。おまけに、ListIteratorのsetってoptionalで、実装されていることが保証されてない。この手の、実装しなくてもいいインターフェイスって撲滅した方がいいと思うんですけどねー。私は怖くて呼べません。

とはいえ、保証されないインターフェイスを完全に撲滅など出来はしません。例えばList(Java)/IList(C#)のAddは実行出来ることが保証されていない。JavaならArrays.asList(1, 2, 3).add(4)を、C#なら(array as IList).Add で簡単にNotSupportedを吐きます。他にもReadOnlyにしたコレクションにインデクサで値をセットしようとすれば、これまた同様に例外。コレクションフレームワークにおいてインターフェイスの完全保証を実現しようとするのは大変難しい。とは、私じゃなくて Java コレクション API の設計に関する FAQでSunが妥協した、と言うぐらいなので、無理なのでしょう。

まとまってませんが、結論としては疑問に思ったらソース読むのが手っ取り早い、とかそんなところで。Javaの良いところはC#に比べてフレームワークのコードへのアクセスが簡単なところですね。C#は部分的にはコードは公開されていて大変タメになるのですが、色々と面倒くさいし、公開されてない範囲も少なくないし……。.NET Reflectorのお世話になりまくってイリーガルな気分を味わうのはもう嫌ぽ。嘘。リフレクタ大好きですがそれはそれ。

Re:Scheduled

Windows7杯では、無事グランプリを逃して部門賞に落ち着いたりした昨今ですがこんばんわ。長らくBlog放置、ついでにゲームも放置中という有様なので、改めてスケジュールを考えなおさないとな、というところに来てたりします。今年はまずはlinq.jsのWSH対応をやるぞ!とか言ってた気がしますがちっともやってませんし。で、代わりに何をやっているかというと、何故かここ一ヶ月は延々とLinqのJava移植をやっていたりします。どうしてこうなった……。予定なんて立てるだけ無駄ってことですな。

そんなわけで、今週中、無理でも来週中にはリリースするつもり、です。もう実装は出来上がっているのですがテストを一切書いてないので、テスト書きとJavaDoc書きと、あと事前条件の例外送出(これも一切書いてない)が残っているので、やらないといけない。実装はやる気満々にノリノリで書けたんですが、残ったこれらをやるのはモチベーションが全然上がらない。ダルぃ。シュタゲやりたい(やれよ)

進捗は例によってneuecc on Twitterでgyazoで画像張りながら、Javaに文句つけながらやってます。スタンダードにwhere-selectとか(NetBeansの赤線は解決しました)、正規表現がスッキリ!とかは、まぁまぁ悪くないと思うのですが、左外部結合で記述がカオスなどは何とも言えないネタ感が。作者の私がメソッドのシグネチャを合わせられなくて苦労したぐらいなので、一体誰が使えるんだよ、という。Join系は鬼門です。あと、匿名型の代わりにTupleを使うのですが、型定義が地獄になるのもねえ。 定番の無限フィボナッチなんかも書いたけど、Tupleの型定義のクドささえなければ見れるのだけど、現状だと少し厳しい。

// static importと合わせるとLLっぽく見えて素敵、的な何か
for (int i : range(5, 10)) {
    System.out.println(i);
}

// 10以上のもののうち重複なしで二倍にしてリストに格納する的な何か
int[] array = {3, 13, 51, 2, 1, 51, 67, 32, 13, 9};
ArrayList<Integer> list = Enumerable.from(array)
        .where(Predicates.greaterEqual(10))
        .distinct()
        .select(Functions.multiply(2))
        .toList(); // [26,102,134,64]

Java7のクロージャと型推論に期待しつつ、今のうちに出来ることはやっておく、的な何か。期待しないで期待して待っててください。多分、思われているよりかはマトモに使える代物だと思います。移植なんて誰もが考えるし、LinqのJava実装なんて当然幾つかあるんですが、どれも全然徹底してない(中途半端にSQLには対応してる)ので、今一つなんですね(JavaScriptの時も同じこと思ったわけだけど……)。ひたすら愚直にLinq to Objectsだけを忠実移植したものは初になると思います。いやまあ、何で誰もやらないかっていうと誰得としか言いようがないから、ってのもありますが。私は、割と普通なLinq好きーなのでそんなのちっとも気にしない!

標準Linq以外のメソッド類はlinq.jsにあるのは大体入れた。linq.jsのRangeDownToはアホだったので削った。RangeTo(0,-10)でいいわけですよね、Toなのだから。DownToとかアホすぎた、といったような反省も若干活かしてます。それとRxのCatchとかFinallyも入れた。良さそうなのは何でもいれますよー。というわけでもないのですけどね、ちょっと便利、程度だと躊躇います。入力補完に大量にメソッドが出るってのはあんまり嬉しいものでもないですから、なるべく厳選して。

デスクトップヒストリー

以前に応募した、Windows 7杯 自作PCの祭典 2009ですが、部門賞「勝手にオレが一番!部門」で無事受賞することが出来ました。部門賞の5台の中から、一般投票でグランプリを決めるので、是非とも私に投票してやってくださいー。あと2/13に秋葉原のリナカフェで当日一般投票&結果発表があるそうなので、そちらも是非どうぞ。あ、私の応募記事ですが→neue cc - デスクトップシアター for Windows 7杯になります。発表会場ではパネルに写真を飾るそうなので、もう少し良い写真を撮っておけば良かったなあ。昔は壁紙を揃えたりカッコつけた照明にしたりしてたんですが、今回は応募締め切りギリギリで必死に写真撮って記事書いて、ってやってたので余裕がありませんでした。若干後悔。

私的には、定格パフォーマンス部門の人のLevel 10が自作PC本体の魅力としてはカッコイイ!ですね。写真の構図が憎い。 あと、バカPC部門の冷蔵庫PCはナイスですな。リザーブタンク=ペットボトルは最高のアイディアだと思います。それにしても、かなり大掛かりに加工してあって、凄いなあ。まさに自作PC、という凄さでは一番ですね。他は、んー、特別賞のマルチタッチ賞が該当なしなのはしょうがないかな。Windows7杯なので、Win7の追加機能であるマルチタッチを!というのは分からないでもないけれど、自作PCでマルチタッチを生かすっていうのは難しいよ。一体何をどうすればいいんだ、という。

写真で振り返る机上写真の歴史

写真が残ってなかったりするのがあるのが悔しいなあ。とか思いつつも、残ってるものを探してきました。ローカルHDDに見つからなくても、Web上に残ってたりして助かりました。やっててよかったWebサイト。といっても、やはり残ってないのも多いのね。残念。

2002年です。VAIOなので自作PCじゃないよ! これは何と言うか、懐かしいね。非常に懐かしい。CRTモニタはともかく、MDデッキとかMIDI音源(YAMAHAのMU2000)とかポータブルCDプレイヤーとか、今じゃあすっかり廃れたものが映っていて泣ける。マウスはIntelliMouse Explorerの、たぶん初代かな。

飛んで2006年です。4年も飛んじゃうのが痛い、こうなるまでにも過程があったんですが……。2002年の写真を見て思いっきり懐かしめたので、もっと残ってると良かったのだけど。既にXbox360だものねえ(画面のものはPGR3)。時代飛びすぎ。この頃はCRT信者でモニタを22インチCRETにしてトランスコーダーで写していましたね。液晶なんて残像!残像!あんなもんクソだ使えねー。CRT解像度最強だし。とか言ってた気がしますが、なかったことにしましょう。ちなみに2chスピーカーはこの頃のほうが良いの使ってました。PIEGAのアルミニュウム筐体のスピーカーでして、音もデザインもとても気に入ってました。サラウンド環境は7.1ch。マウスはMX1000かな、これは。多ボタンマウス信者の始まりである。

2007年の8月。いつのまにやら全モニターが液晶になってる。やっぱ液晶だとスタイリッシュな感じになりますなー。大きさは全部WUXGA(1920x1200)。サラウンドは、センタースピーカーなしの6.1chという若干変則的な形を取っていました。デスクトップシアターだとセンタースピーカーの置き場に困るんですよねー。この問題を解決させられなくてずーっと悩んでいました。ま、この頃はセンタースピーカーは左右スピーカーの中央で聞けるのならなくても問題ない。一人で椅子に座って聞くデスクトップシアターではファントム再生(左右のスピーカーでセンタースピーカーの音を仮想的に作る)でも問題ない。むしろ、そのほうが音の統一感が出ていい。とか言っていましたが、なかったことにしましょう。マウスは恐らくMX-R。キーボードはHappyHackingKeyboardですなー。デザインは最高に好き。でも、今は日本語配列のRealForceになりました。

2007年の11月。これはデスクトップシアターの薦めという記事で書いた時のもの。スピーカーを小型スピーカーに変更したことで、9.1ch環境になりました。スペース的にも大型スピーカーを廃したことで余裕が出て正解だったと思っています。センタースピーカーも、(モニタ土台のほうの)デスク下に配置することで解決。PIEGAのスピーカーとの決別と、それによる2ch再生力の低下に悩んだんですが、それは今ではSTAXで聴くからいいもん、という方向で解決(?)しました。

2008年の11月。こりゃまたえらく簡素になったねー、というわけですが、これは引越しして一人暮らしを始めた時の写真です。モニタとかデュアルでいいっすよ、とか思って色々と整理したはずなんですが、やっぱりデュアルでは不満になったので結局、買い揃えていくことになったという。なんだかなー。ともかく、新生活のフレッシュさを感じたり感じなかったりして、ちょっと思い出深い。涙ちょちょぎれます。で、まあ、私は家にいる時は、このデスク左にちょっと映ってるベッドで寝てるか、椅子に座ってるかの二択しかありません。NEETになりたいなあ。職場なんて行きたくないでござる。

2009年の2月。これはneue cc - 解像度6000オーバーという記事で書いたもので、メインモニタがついに30インチになりました記念。メインモニタとあわせて左右のモニタも新調しました。ちなみに、せっかくの縦回転ですが、今の環境だと縦が塞がっているので出来ないんですよねえ……。トレードオフといえばそうなのですけど、この頃がちょっと懐かしい。

2010年の1月。これがneue cc - デスクトップシアター for Windows 7杯の写真で、今の環境です。9.1chなのですが、AVアンプを変えたので全方位を横一列に取り囲んでの9.1chじゃなくて、フロント上部に2台設置しての9.1chという環境に変わりました。このハイトスピーカーの置き方は最近出たドルビープロロジックIIzという規格によるもので、徐々に増えて行くんじゃないかと思っています。まあ、あとは再びクアドラプルモディスプレイ環境に戻ったり、ノートPCがあるから5画面だよ、といった感じであったり、今までの集大成的なものになっています。

といったわけで、駆け足で振り返ってみました。やっぱ2002-2006の間の写真がないのが痛い。絶対後悔するので、写真はちゃんと残しておきましょう、まる。ブログにアップしておけば、サルベージも容易でいいね!ていうか、実際2002年の写真以外はローカルに残ってなくて全部ウェブから引っ張ってきたのですが……。やっててよかったWebサイト。あと、写真がちっとも上達しないのが酷い。むむむ。

AnonymousComparer - ver.1.2.0.0

AnonymousComparerを再度バージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。バグがなければ、これで最後だと思います。いやもう内容的には出尽くしたかな、と。更新内容はIComparer<T>を作成可能にしました。また、OrderByでIComparer<T>を利用するものへ、拡張メソッドを追加しました。

// こんなシーケンスがあるとして、IComparer<T>を使用してその場で自由に比較を指定したい
var seq = new[] { 1, 2, 3 };
// IComparer<T>を作る
var comparer = AnonymousComparer.Create<int>((x, y) => y - x);
seq.OrderBy(x => x, comparer);
// OrderBy/ThenByに拡張メソッドが追加されているので、型推論が効いたまま書けます
// List.Sort(Comparison)みたいなイメージですかね
seq.OrderBy(x => x, (x, y) => y - x); // 3, 2, 1
seq.OrderByDescending(x => x, (x, y) => y - x); // 1, 2, 3

LinqにはDescendingが用意されているので、あまり使い道はなさそうですね。私もOrderByのICompare<T>オーバーロードを使いたいと思ったシチュエーションが今までにありませんし……。第一引数がkeySelectorなので、それで十分用を足せちゃうのですよね。それにしても、DescendingでIComparerを指定した場合の結果は紛らわしくていかんですな。

さて、更新内容はもう一つあって、むしろこっちのほうが重要なのですが、compareKeySelectorを利用したオーバーロード(Linq演算子への拡張メソッドは全部それです)で、シーケンスにnullが含まれている場合にnullで落ちるのを修正しました。今回からはヌルぽで落ちません。どういうこっちゃ、というと説明しづらいのでコードで。

class MyClass
{
    public int MyProperty { get; set; }

    public override string ToString()
    {
        return "Prop = " + MyProperty;
    }
}

static void Main()
{
    var array = new[]
    {
        new MyClass{MyProperty=1},
        null,
        new MyClass{MyProperty=2},
        null,
        new MyClass{MyProperty=1}
    };
    var r1 = array.Count(); // 5
    var r2 = array.Distinct().Count(); // 4 (nullが重複として消える)
    foreach (var item in array.Distinct(mc => mc.MyProperty))
    {
        Console.WriteLine((item == null) ? "ヌルぽ" : item.ToString());
    }
    // 出力結果は
    // Prop = 1
    // ヌルぽ
    // Prop = 2
}

といった感じです。分かったような分からないような?

ver.2.1.0.1

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

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

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

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

AnonymousComparer - ver.1.1.0.0

AnonymousComparerをバージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。更新内容はCreateのオーバーロードに追加して、IEqualityComparerの完全模写を可能にしました。今まではキー選択だけだったのですが、今回からはEqualsとGetHashCodeを個別に指定することが可能です。

var myClassComparer = AnonymousComparer.Create<MyClass>(
    (x, y) => x.MyProperty == y.MyProperty, // Equals
    obj => obj.MyProperty.GetHashCode()); // GetHashCode

こんな感じに指定します。全くもってそのままです。Equalsとかをラムダ式で指定するというだけです。型推論は効きませんので指定してやってください。なお、使い方自体は全然変わってませんので、普通の使い方は初回リリース時の記事を参照ください。

以下オマケ。

// ランダムでtrueかfalse返すComparer
var rand = new Random();
var randomComparer = AnonymousComparer.Create<MyClass>(
    (_, __) => rand.Next(0, 2) == 0, _ => 0);

// 間引くのに使えるぜ!
var mabiita = list.Distinct(randomComparer).ToArray();
// とか思ったけど、最初の一つ目は必ず選択されるのよね(Distinctなので当然……)
// といった、特殊な間引き方をしたい人はどうぞ(いません)
// 普通にやるならWhereでランダムにフィルタリングするのを選びます

色々と用途が考えられるようで、そもそもEqualityComparer自体があまり使う状況ってないので、使い道が考えられない微妙な感じが素敵です。まあ、AnonymousComparer自体はふつーに使う分にはふつーに便利ですので、Linqのお供にどうぞ。もう一つ、今度は役に立つ例でも。

class MyClass
{
    public int Prop1 { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
}

static void Main()
{
    var array = new[]
    {
        new MyClass{Prop1=30, Prop2="hoge", Prop3="huga"},
        new MyClass{Prop1=100, Prop2="foo", Prop3="bar"},
        new MyClass{Prop1=100, Prop2="hoge", Prop3="mos"},
        new MyClass{Prop1=30, Prop2="hoge", Prop3="mos"}
    };

    // Prop1とProp2が一致するのだけ省きたい!といった複数キー指定は匿名型を作る
    array.Distinct(mc => new { mc.Prop1, mc.Prop2 });
}

複数キーで比較したい時は、匿名型を作るのが手っ取り早いです。こういった用途に匿名型を使うというテクニックは、Linqの他のところでも結構出てくるので覚えておくと便利です。富豪的?気にしない気にしない。

Prev | | Next

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2025

X:@neuecc GitHub:neuecc

Archive