ver 1.1.0.1 日本語が含まれる場合に正しく投稿されない問題を修正

neue cc - XboxInfoTwit

表題通りの修正、です。これが酷い話で、大往生を買った頃に不具合が発覚していたというのに、今の今まで放置していたという。指摘があって(ありがとうございます!) ようやく重たい腰をあげて……。しかも大体どこが問題発生の場所なのか検討ついてたし、事実その通りだったし、しかも一行書き換えるだけで終わる程度の話だったうえに、不具合原因が超凡ミスだったり、それなのに今の今まで放置していたし、と二重三重に酷い話です。

もう本当にごめんなさいとしか言いようがない。

JavaScriptでString.Format

function Format(format /*,obj1,obj2...*/)
{
    // 置換文字に対応する配列
    var dict = E.From(arguments).Skip(1).ToArray();
    // 正規表現用の文字列( {(0|1|2)} )
    var str = "\\{(" + E.Range(0, dict.length).ToString("|") + ")\\}";
    var regex = new RegExp(str, "g");
    return format.replace(regex, function(m, c) { return dict[c] });
}

var result = Format("食べ物は{0}で飲み物は{1}だそうです。", "タコ焼き", "コーラ");
document.write(result); // 食べ物はタコ焼きで飲み物はコーラだそうです。

(C#の)String.Formatは便利!お手軽!JavaScriptでも使いたい!ていうかprototype.jsのTemplateは書き方がヘヴィすぎだしね!というわけでサクサクッとその場使い捨てなフォーマット記述関数を、linq.jsを使って(←ここ重要)作りました。

やってることはごくごく単純で、正規表現でガッと置換するだけです。その正規表現を生成するのにlinq.jsが役立ち……ってほど役立ってないというかあんまし使ってませんね。別に決め打ちで数字だけマッチさせるのなら、余計な手間もループして結合もいらないし。うわ……。

function Format(format /*,obj1,obj2...*/)
{
    var args = arguments;
    return format.replace(/\{(\d)\}/g, function(m, c) { return args[parseInt(c) + 1] });
}

はいはい。別に使わなくてもたった二行でしたねすみませんすみません。次はもっとマシな例を考えてきます。最近脳みそがゆるふわ化しててほんとすみませんすみません。

追記:私の制作しているライブラリlinq.jsのアドオンという形で、機能強化したフォーマット用のコードを置いてありますので良ければそちらも参照ください。linq.jsと周辺については最もタメになる「初心者用言語」はVisualStudio(言語?)にまとめました。

linq.js ver 1.2.0.0 - バグフィックスとEqualityComparer

バグフィックス

JavaScriptのオブジェクトはハッシュ。というから、ああ、はいはい、つまりDictionaryなんですね、と素で何の疑問も持たず、思っていました。キーには別に数字でもオブジェクトでもなんでも、そう、オブジェクトでも入れられると思っていて、平然とそれを大前提にしたコードをlinq.jsにも混ぜ込んでいました。集合演算系のものに。ありえないです。C#脳すぎる。

var hashTest = {};
var obj = { hoge: "fuga" }
var obj2 = { tako: "ika" }
hashTest[obj] = "tettette";
hashTest[obj2] = "totetotetote";
hashTest["[object Object]"] = "12345";
for (var key in hashTest)
    alert(hashTest[key]);

これは当然のようにアラートで出てくる値は"12345"だけです。JavaScriptのオブジェクトはキーが文字列限定のDictionary(C#脳ですみません)で、キーに文字列以外のものを突っ込んだ場合、自動的に文字列に変換される。だからオブジェクトは文字列に変換され、オブジェクトが文字列化される際は全て"[object Object]"になるから上書きされる、というお話。そんなわけで、今までのlinq.jsでは……

var seq = E.Range(1, 10).Select("{test:$}"); // {test:1},{test:2}...
var count = seq.Distinct().Count(); // 重複を除いたものの合計、当然10なのに?
alert(count); // 結果は1です、ごめんなさい

となっていました。これは酷い。ちなみに、何故かFirefoxでは10になります。IEやChromeは1なんですけどね。linq.jsの内部の動きから考えるに、Firefoxのほうが変な挙動なのは確かなのですが良くわかりません。ただ、ブラウザ間で互換の取れてない動きが出てしまう、というのもまた良くない話です。そんなわけで直しました。めでたしめでたし、のようでいて複雑な気分。というのも内部コードの実行効率が酷いことになってしまったからです。下のコードはkeyが含まれているかどうかを調べる関数の一部なのですが

if (Linq.HashSet.IsPrimitive(key))
{
    return this.PrimitiveContainer.hasOwnProperty(key);
}
else
{
    for (var i = 0; i < this.ObjectContainer.length; i++)
    {
        if (key === this.ObjectContainer[i]) return true;
    }
    return false;
}

最初にPrimitive、つまり数字や文字列であるかを判断し、プリミティブであればhasOwnPropertyでキーがあるかないかを判断。こっちは今まで通り。そしてそのままではキーとして使えないオブジェクトに対しては、線形探索で全部舐めて探してるんですね―。うはー、大量の配列に使った場合の遅さが怖すぎる。ただ、この辺のやり方はJavaScript Hashtableなんかも同じような内容なので、しょうがないみたいです、素のJavaScriptでは。ハッシュコードを算出するために重たい計算を挟めば、それはそれで本末転倒ですし。

EqualityComparer

JavaScriptのObjectは参照型、なので全ての値が同一であろうとも、別のオブジェクトであるのなら別だと判断される。しかし、さすがにそれでは実用性に乏しい。

// Before
var a1 = E.Repeat("dummy", 10).Select("{test:$}").Distinct().Count();  // 10
// After
var a2 = E.Repeat("dummy", 10).Select("{test:$}").Distinct("$.test").Count(); // 1

{test:"dummy"}を10個生成し、それを重複除去するとしたら、望む値は当然、1。そういった動作が出来るようにDistinctなどの集合演算系にキー指定が出来るようになりました。そういえばC#の匿名型はクラスなのに重複除去されるのは何でだろう、と思ったらちゃんと「すべてのプロパティが等しい場合のみ、等しい」ように作られているようです。さすが、ユーザーがどういう動作を望んでいるのか分かってる。

Contains, Distinct, Except, Intersect, SequenceEqual, UnionにcompareSelectorを追加、という形になります。比較関数にするかキーセレクターにするかで悩んだのですが、比較関数だと使用時の定義が面倒くさいので、実用性を考えて簡易的なもののほうが良いかな、と思いキーセレクターにしました。9割以上は同じものの比較ですよねー、という決めつけです。複数の値で比較したい時は適当に連結する、しかないです。塩でも振りながら文字列にして結合してください。お手軽でそこそこ確実な手を言えば、ToJSONメソッドを使ってJSONに変換するのも手です。

var seq = E.Range(1, 10).Select("{key:$<5,evenodd:$%2==0}");
seq.Distinct("E.Repeat($,1).ToJSON()").Count(); // 4

true:false,true:true,false:true,false:falseの4通りのどれかが10個なので、重複除去すると合計数は4つ。ところでそうそう、集合演算で線形探索とかフザけるなタコ、と思った場合はこのようにJSON化することで(値参照になりますが)回避できます。何かバッドノウハウみたいで素敵。ただ、そもそもオブジェクトにたいしてキー指定無しの集合演算自体が使う機会なんてほとんどないとは思いますが。

linq.js ver 1.1.0.0 - Linq to Xml for JavaScript

linq.js - LINQ for JavaScript Library - Home

linq.xml.jsとしてver 0.0.0.1を同梱しました。更新系はほとんど未実装ですが、抽出系のメソッドで主要なものは動いているので、お試しぐらいには使えると思います。linq.xml.jsなしで書くとしたら、ああ、ここでfor回して、ああ、ここで再帰書いて、もう! 面倒くさい面倒くさい、と思ってしまうわけなので素の状態と比べたらとっても役立ちです。

サンプルとしてTwitterビューアを同梱しました。AttachEventをつけた結果、限りなくAjaxライブラリな状態になってしまいました。まあ、この辺はオマケみたいなもので、基本的には抽出->加工がメイン、なはずです。あとドメイン間の通信は当然出来ないので、IEの場合はインターネットオプション→セキリティ→レベルカスタマイズ→ドメイン間データソースアクセスを有効にしてください。Firefoxでのやり方は分かりません。ただのデモで実用的なものでもないので、IEで見ていただければと思います。サンプルなので、チュートリアルがわりに解説します。

// window.onload時に動く関数を登録する
X.Initialize(function()
{
    // ボタンを特定して、クリックイベントを登録する
    X.ID("description")
        .Elements("input")
        .Where("$.Attribute('type').Value()=='button'")
        .Single()
        .AttachEvent("click", UpdatePanelFromTwitter);
});

function UpdatePanelFromTwitter()
{
    var twitterBasePath = "https://twitter.com/statuses/user_timeline/";
    var placeHolder = X.ID("placeHolder"); // IDからXElementを取得
    placeHolder.SetValue("Loading..."); // Element以下の要素を消去してテキストを追加
    var userID = X.ID("userID").Attribute("value").Value(); // UserIDを属性から取得

    // 非同期でXML読み込み
    X.Load(twitterBasePath + userID + ".xml", function(rootXml)
    {
        placeHolder.RemoveNodes(); // Loading表記を削除
        rootXml.Descendants("status").Select(function(xEle) // XMLからオブジェクトにマッピング
        {
            return {
                created_at: xEle.Element("created_at").Value(),
                id: xEle.Element("id").Value(parseInt),
                text: xEle.Element("text").Value()
            }
        }).ForEach(function(obj) // そのオブジェクトを使ってforeach
        {
            var elem = X.CreateHTMLElement("p");
            elem.Add(obj.id + " : " + obj.created_at);
            elem.Add(X.CreateHTMLElement("br"));
            elem.Add(obj.text);
            placeHolder.Add(elem);
        })
    });
}
<body>
    <div id="description">
        TwitterID:<input type="text" id="userID" value="" /><input type="button" value="Refresh" />
    </div>
    <div id="placeHolder">
    </div>
</body>

X.Initializeはwindow.onloadに対するイベント登録です。こういう、Linq to Xmlとは全く関係ないメソッドは入れたくなかったのですが、やむをえず。最初はX.Body()でdocument.bodyが取れるので、それにたいしてAttachEventでいいかなーと思ったのですが、ヘッダ内だとbodyがまだ生成されていないのでイベント登録できなくて、特例的にこのメソッドを作りました。

そして、登録するものはボタンを押した時に発生するイベントの登録用関数。わざわざdescriptionから辿っていますが、IDが振ってあればX.ID()でダイレクトに取れます。やっていることはメソッドチェーンの順番通りで、IDが"description"の要素下の -> Element名が"input"の -> Attribute名が"type"で値が"button"である -> 唯一の要素に対し -> イベントを登録。思考の流れ通りの、非常にシンプルで分かりやすい登録方法だと思います。

XMLを読み込んでいるX.Loadですが、これは第一引数がファイルパス、第二引数がコールバック関数になります。第二引数を省略した場合は同期通信になります。例えばvar xml = X.Load("hoge.xml")という使い方が出来るわけです。ローカル環境でテストしたい場合は同期通信のほうを使った方がやりやすいかと思います。

Descendantsは子孫ノードの取得。XMLの要素取得では最も使われるものだと思いますが、ぶっちゃけgetElementsByTagNameです。その次のSelectは射影です、ようするにmapです。Linq to Xmlのキモの部分で、XMLをオブジェクトに変換します。xEleには"status"以下の要素が順番に渡されていくので、それに対してElement("要素名")の値(Value)を取得。といった流れ。Value()には変換関数を渡すことが出来るので、stringじゃなくintにしておきたい、という場合にはparseIntなんかを入れると良いです。

ForEachは、Selectを通って出来たobjectに対してforeachです。今回はpタグのエレメントをつくって挿入してplaceHolderに追加-。ということでした。ここの要素追加のaddは、ようするにappendChildなわけで、本来のLinq to Xmlではもっと洗練された形になっているので修正するつもりです。今は暫定ということで、この形での登録しか出来ません。あとCreateHTMLElementということで、HTMLとXMLを区別しているのも頂けないですね……。ここは統一したやり方で何とかなるよう調整したいと思います。そのうち。

FAQ

  • わざわざjQueryじゃないこんな意味不明粗製ライブラリの使い方を覚える必要あるの?

それ言ったらXPathイラネ、とかE4Xイラネ、とかにも繋がりますし。そんなに比較するようなものじゃないと思います。出来ることの幅も全然違いますし。あくまでこちらはリスト/DOM操作のみです。ただし、リスト/DOM操作でのみ言えばこちらのほうが多くのことがスムーズに出来ると思います。C#のLINQは本当に良く出来てるライブラリだから、というわけで、主な利点はC# Linqの学習ができること、です。中身はともかく表面的な模写としてはわりと忠実なので、C#でも同じ書き方で十分行けます。

  • で?

私が喜びます。Disって欲しいんだって、ほんとうに。0反響よりは100のDis。

  • そもそもlinq.jsって何?

初回リリース時のチュートリアルをどうぞ。

そしてXPathに戻る

Linq to Xmlのメソッド群はXPathにそれぞれ対応する。XPathを覚えることなく、メソッドを繋げていくことで対象ノードが絞りこめる。これによる利点は、IntelliSenseが効くので高速にタイプセーフに思考の流れのまま記述出来るということ。例えばrootというXElementから「h1要素のうちクラス名がhogeの親をリストアップする」ならば

root.Descendants("h1")
	.Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "hoge")
	.Select(e => e.Parent);

……あれ? オカシイデスネ。簡潔とは程遠い勢いです。せめてWhereでのAttributeの参照をもう少し綺麗に書ければ……。nullが敵すぎて困る。そんな時のため、というわけではないのでしょうがSystem.Xml.XPath名前空間をインポートすることで、XElementにXPathSelectElementsという拡張メソッドが追加されます。これで、

root.XPathSelectElements("//h1[@class='hoge']/..");

と、簡潔明快に記述できる。ふむ。まあ、そりゃXPathのほうが簡潔になるのは「当たり前」ですもの。この程度ならまだいいけど、複雑怪奇になったときは(正規表現と同じように)解読出来ねえよ、と。個人的には全然LINQのほうが好きですよ!nullに気を遣う必要がなくなれば。ていうか、XPath->IEnumerableは別に普通にLinqですけど。やー、何かもうDescendants使う機会が無くなってしまう気がするなあ。

で、linq.jsではXPathの実装までやるつもりはありません。あー、解析してLinq形式に変換する程度ならやるかも。まあ、はっきしいって、愚直にイテレータ回してるので速度でいったら明らかに遅いんですけどね。翌々冷静に考えるとネタというか色物としかいえない仕上がりな気がしてきましたよ。みんなJavaScript-XPath使えばいいさ……。

そういえば、XAttributeのValueは代入可能で、当然代入すると即座に反映されるわけなんだけど、JavaScriptだとメソッドが擬態したプロパティって無理ですよね?Value()とSetValue()に分けるしかないのかなあ。XElementがその形なので違和感無いといえば無いんですけど微妙(そもそもXElementのほうもValueプロパティ一個でいいのに、何で分けたんだろう)。誰か良い案あればください。

今日のlinq.js

誰得状態になってもちゃんと書き進めてますので安心。あ、linq.jsダウンロード50到達ありがとうございます。次はLinq to Xml搭載で100ダウンロード到達を目指します。で、ええ、と、子ノードを全部削除するよくあるメソッドを作ろうと思ったのです。XElementにも当然あります、「RemoveNodes」です。

this.Elements().ForEach(function(e) { e.Remove() });

thisはXElementで、Removeは自身を親ノードから削除するというもの。Elements()は子ノードを列挙するものなので、そのままForEachで削除していけば出来あがり。ではないというよくある話を普通にやりました。たはー。列挙しているものの長さ(childNodes.length)がRemoveで変わっていってしまうんですね。

E.From(this.Elements().ToArray()).ForEach(function(e) { e.Remove() });

ToArray()でコピーを作って列挙。これなら問題なく動く。これがスナップショットって奴でしょうか? コピーのロスがあるので逆順forとかwhile(firstChild)使えよって話ではありますけど、今更速度どうこう言ってもしょうがないので普通に無視。と言いつつ、書き直しましたけど。ただ、prototype.jsでも内部で同じようなことやってるようなので大丈夫(何が?)

$A(element.childNodes).each(function(node) { element.removeChild(node) });

やっぱ、Arrayを直接拡張してeachが使えるとスッキリしていいですねえ。うーん、E.From(E.From(要素).ToArray())のショートカットメソッドが欲しいかもしれない。でも、名前が思いつかない。Cloneというのも変だし……。CopyDoとかどうでしょう、ダサいですかダサいですね。ていうか意味不明ですね。

Elements: function(name)
{
    var elems = Linq.Enumerable.From(this.Source.childNodes)
        .Where(function(elem) { return elem.nodeType === Linq.Xml.NodeType.Element });
    if (name != null)
    {
        elems = elems.Where(function(elem) { return Linq.Xml.Utils.StringCaseCompare(elem.nodeName, name); });
    }
    return elems.Select(function(elem) { return new Linq.Xml.XElement(elem); })
},

いやー、LINQは条件を好き勝手あとで継ぎ足していけるのがいいですよねー。で、再掲したのは前回のが間違っていたから。前回のElementsの例でpタグのものを取った時、TextNodeも取得しちゃってたのは完全に間違いです。TextNodeも含めたい時はNodes()メソッドを使い、Elements()ではNodeTypeがElementのものしか取れない。というように直しました。如何せんC#のLinq to Xmlの知識自体が欠けているので、Nodesメソッドを実装しようとしてから初めてこの事に気がついたという有様。Nodes()の戻り値はXNodeなんですね、XElementとXTextが混在するから……。

そういえば、要素名を大文字小文字を区別すべきか、しないほうがいいか悩み中。C#のLinq to Xmlでは当然区別しているんですが、区別するとWhere使うときにタグの大文字小文字区別が面倒くさいとかあるので、区別なしにしちゃおうかなー、とは思ってます。

linq.js to Linq to XML/DOM

Linq to Xmlの移植を始めました。やはりJavaScript上で最も動かすことになるリストってDOMなので、欠かかせません←結論。これで大幅に使い道や表現の幅が広がりそうです。ただ、あまりやり過ぎるとjQueryでいいぢゃん、的な捉えられかたをされてしまいそう。個人的には全然用途が違う、共存できるものだと思っているのだけど。

前回書いたC#でのLinq to Xml例と比べても、ほとんど書き方は一緒です。linq.jsではLinq.EnumerableのショートカットとしてEを使いましたので、今回はLinq.XElementのショートカットとしてXを使うことにしました。アルファベット一字系はちょっと危険かなあ、というところがあるので要再検討、でしょうか。

値を得るValue()は、キャストのかわりにパースする関数を引数で取るようにしました。Value(parseInt)で数字が取れるわけです。カッコが一つ減って、少し見通しが良くなる。地味に便利です。多分。あと、Value()で取れる値はIEのinnerTextと同じです(多分) FirefoxでinnerTextが使える!というのは利点にならないでしょうかならないですか。取得専用だし。

実際の書き方ですけど、div->h1,h2,h3は見たまんまにシンプルなのですが、p->textのほうはちょっと複雑になってしまいました。慣れないと若干厳しめ? SelectManyはノード操作時に便利です。Elements("p")だけでは、1個のpタグのノードしか取得できないので、更に展開するためにSelectManyを使います。展開するものの指定は、childNodeを取りたいので$.Elements()。引数(タグ名)無しだとchildNodeを全て展開します。これで[tako,br,huga,br,hoge,br]というXElementが取得できます。第二引数はresultSelectorということで、平坦化の後の射影です。今は値が欲しいだけなので、Value()を利用。これで結果は[tako,null,huga,null,hoge,null]になりました。nullは、brタグのValueです。これはいらないので、最後にWhereで除去してやれば出来あがり。

Elements: function(name)
{
    var base = Linq.Enumerable.From(this.Source.childNodes)
        .Select(function(elem) { return new Linq.XElement(elem); });

    return (name == null)
        ? base
        : base.Where(function(elem) { return elem.Source.nodeName === name.toUpperCase(); });
},

Elementsのソースコードは今のところこんな感じです。Linq to Objectsという基盤があるので、それを適用することに特化したDOM Elementのラッパー、ということになります。C#のLinq to Xmlがそうであるように、取得だけじゃなくて、反映までやりたいですねえ。とりあえず、今のままでもXMLのパースぐらいになら十二分に使えると思うので、linq.jsの次回更新時にテスト版として入れときます。

C#とLinq to XmlとXPath

どうも、先人が3年前に通り過ぎた道を今更辿ってる私ですがこんばんわ。そんなことを思うと虚しいので、せいぜい高速道路!高速道路!と叫ぶことで再舗装したいと思っております。ていうかさー、JavaScriptでゆるふわ系でキャッキャウフフだと思ったのに人格に問題あるのは私ですかー、みたいなー。Xbox360並に殺伐。しょうがないので、リハビリC#。そろそろXbox360に戻りたいのですが、プログラミングはスイーツ(笑)なので甘美ですね、ていうかXbox360は殺伐としすぎだろ常識的に考えて。アウトランNAIJとか。

デススマをスルーしてX-BLADEを買う俺カッコイイ、そう思っていた時期なんてありません。でもデススマはスルーしそう。何というかゲーム脳が恐怖で最近のXbox360へのモチベーションのなさは近年なく異常。どのぐらいかというと、ああもうLinq to XmlもJavaScriptに移植したいんだけど時間考えると結構厳しいかもだーあーもう、ぐらいな感じ。一応、今年はXNAに手を出したいそうなんですが手を出せるのかなあ。何かモチベーションがふらついてますねえ。Linq愛は何故か冷めないどころか燃え上がってるのですけど、斜め上方向に突っ走ってる気がしてならない。

興味という点では、モバイル機器にたいして全く興味が沸かなくなってしまったということをつい数時間前に実感した。Windows MobileもiPhoneもAndroidもどうでもいいですよ、だって私、家にずっといるからモバイル機器触る機会が全くないんだもの。それじゃあ関心とか持つの無理だわ。関心持てないものには何のアイディアも沸かない。うん、ダメダメ。興味なんて天の授かり物なんだから、何かが降ってくるのをただ黙って祈るだけです。今のところ、降ってきている関心であるLinqに、今のうちだけだからこそ全力を注ぎたい。力不足でも全力で。3年前の道だろうと全力で。

というわけで、Linq to Xml。実のところ翌々考えてみるとあまり使ったことがなかったので、ちょっと使ってみる。livedoor 天気情報のRSSを引っ張ってくるとして……

var root = XElement.Load("http://weather.livedoor.com/forecast/rss/13/63.xml");
var result = root.Descendants("item")
    .Where(elem => elem.Element("category").Value != "PR")
    .Select(elem => new
    {
        title = elem.Element("title").Value,
        link = elem.Element("link").Value,
        day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), elem.Element("day").Value),
        description = elem.Element("description").Value,
        pubDate = (DateTime)elem.Element("pubDate"),
        image = Enumerable.Repeat(elem.Element("image"), 1).Select(img => new
        {
            title = img.Element("title").Value,
            link = img.Element("link").Value,
            url = img.Element("url").Value,
            width = (int)img.Element("width"),
            height = (int)img.Element("height")
        }).First()
    });

えー、XmlDocumentー?XmlDocumentが許されるのは小学生までだよねー。というわけでXDocument/XElement。DateTimeやintに直接キャスト出来るんですね!これは地味に超便利、イイ!

この天気予報RSSはimage以下が更に展開できるようになっているようなので、入れ子で。image以下のものに対して毎回「elem.Element("image").Element("title").Value」といったように書くのが面倒くさいので、1回だけのRepeatを使ってSelectを使えるようにしてやって、First()で戻してやる。うーん、どうなんだろう……。クエリ構文だったらletを使うところでしょうか。

まあしかし実のところ、Elementにこだわらず、Elements("image")にすればEnumerable.Repeatなんぞ必要ないのであった。実質1回リピートだから。ただ、どちらにせよFirst()は必須なんだけど。この辺、何ともしっくりこないなあ。

var xDoc = new XmlDocument();
xDoc.Load("http://weather.livedoor.com/forecast/rss/13/63.xml");
var items = xDoc.SelectNodes("//item"); // Descendants
foreach (XmlElement item in items) // varが使えなかったりする
{
    // これがWhereのかわり
    if (item.SelectSingleNode("category").InnerText == "PR") continue;

    // クラス作ってListにAddだと思うのですが、省略
    var temp = new
    {
        title = item.SelectSingleNode("title").InnerText,
        pubDate = DateTime.Parse(item.SelectSingleNode("pubDate").InnerText)
    };
}

比較のためにXmlDocumentでも書いてみた。foreachがとても忌々しく邪悪なものに見える……。あとInnerTextも良くないよねえ。ただ、思っていたほど見た目に違いはなかった。Linq to Xmlでコード超短縮!とか思ってただけに微妙に残念。ようするにXPathがメソッドの形で書けるのがXElement、といった感じなのかなあ。IntelliSenseがガシガシと効くので、XPath的なことを覚える必要がないのは大変良いです。

JavaScriptに移植できるかなあ。でもまあ、素のJavaScriptが恐ろしくXML操作し辛いのは確かなので補助ライブラリがないとやってられないのは確か。XPathすらついてないなんて知らなかった!

実践linq.js リンクフェーダーの作り方

実践というか、普通に使えそうな例を出して販促活動しよう、と思ったので、linq.jsを使ってリンクフェーダーを作ってみました。リンクにマウスカーソルを乗せると色がフェードしながら変わるってものを作ります。とりあえずサンプル。このサイトで使用しているものそのままなので、#e10000に向かってフェードします。色の差が激しいもののほうが効果が分かりやすいと思います、ので、このサイトでは気付かないぐらい地味な変化です。気付かないぐらいが丁度良いという美学だそうです。

サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル

神は細部に宿る。こういう、地味なところにも神経が行き届いていると、良く出来てるなあ、と私は思うのですがどうでしょう? 地味ではありますが、最も目に触れる部分でもあるので演出効果はわりと高いと思います。では、ソースを見ていきます。

// customize : here

var _targetColor = "#FFFFFF"; // #RRGGBB
var fadeSpeed = 400; // millisecond

// customize : end.

customize : here(笑) というしょうもない英語力が光る出だしに萎える……。_targetColorは目標色。普通はa:hoverと同色を用います。というか、このスクリプトを適用するとa:hoverのほうが無効化されます。fadeSpeedのほうはフェードにかかる時間で、10000とかにすると、とってもゆーっくりフェードします。1秒ですら意外とウザく感じるので500以下がお薦め。

// Global Variable

var ColorTable = { aliceblue: 'f0f8ff', antiquewhite: 'faebd7', aq...

var HexToDecTable = (function()
{
    var numbers = E.RangeTo(0, 9);
    var alphabet = E.RangeTo('a'.charCodeAt(0), 'f'.charCodeAt(0)).Select("String.fromCharCode($)");
    var digits = numbers.Concat(alphabet);
    var colors = E.From(digits).SelectMany(function(i)
    {
        return E.From(digits).Select(function(j)
        {
            return i.toString() + j.toString();
        })
    });
    return colors.Select("v,i=>{v:v,i:i}").ToObject("$.v", "$.i");
})();

var DecToHexTable = E.From(HexToDecTable).ToObject("$.Value", "$.Key");

var targetColor = new Color(_targetColor);
var resolution = 10;

全体で使う変数。変数っていうか定数です。constがあれば全部constにしたいですね、これらは。ColorTableは、色指定が色名で来た場合に16進数の文字列に変換するためのもの。IE以外だと10進数のRGBで来るんですけど、IEは色名で指定されていると、そのまま色名で返してくるのでこういうのが必要なんですね、UZEEEEE。

HexToDecTableは16進数の色表現を10進数の色表現に変換するためのテーブル。わざわざテーブル作らないで普通に16進->10進変換関数を用意すればいいだけの気もしますが、このテーブルを作らないとlinq.jsの出番がないのです。ていうか実はlinq.jsの出番はここだけだったりします。

中身は前回のサンプルとほとんど同じで、0-9とa-fを連結して一つにしたら、それの直積で全組み合わせを出して00-ffの256通りを作る。その後の「Select("v,i=>{v:v,i:i}").ToObject("$.v", "$.i")」は、v(value)が生成した16進数の表現で、i(index)が0から255までのインデックスなのですが、ちょうど10進数の表現になってます。それをToObject、つまりキーが16進数表現、値が10進数表現のオブジェクトに変換します。

画像で見るとこんな感じ。DecToHexTableは、HexToDecTableのキーと値をひっくり返したものです。ひっくり返す処理をたった一行でサクッと記述出来るのは、地味に便利。残る二つ、targetColorはまんまで、フェードの目標色です。Colorクラスはすぐ後で説明します。resolutionは解像度、フェードの滑らかさです。つまりはSetIntervalの更新間隔です(笑)

// Class

function Palette(dec, hex)
{
    this.Dec = dec;
    this.Hex = hex;
}

function Color(strColor)
{
    strColor = strColor.toLowerCase();
    if (ColorTable.hasOwnProperty(strColor)) strColor = '#' + ColorTable[strColor];

    if (strColor.indexOf("rgb") != -1)
    {
        var array = strColor.split(",");
        var decR = parseInt(array[0].substring(4, array[0].length));
        var decG = parseInt(array[1]);
        var decB = parseInt(array[2].substring(0, array[2].length - 1));
        this.R = new Palette(decR, DecToHexTable[decR]);
        this.G = new Palette(decG, DecToHexTable[decG]);
        this.B = new Palette(decB, DecToHexTable[decB]);
    }
    else
    {
        var hexR = strColor.substr(1, 2);
        var hexG = strColor.substr(3, 2);
        var hexB = strColor.substr(5, 2);
        this.R = new Palette(HexToDecTable[hexR], hexR);
        this.G = new Palette(HexToDecTable[hexG], hexG);
        this.B = new Palette(HexToDecTable[hexB], hexB);
    }
}

Color.prototype.SetFromDec = function(decR, decG, decB)
{
    this.R.Dec = decR;
    this.R.Hex = DecToHexTable[decR];
    this.G.Dec = decG;
    this.G.Hex = DecToHexTable[decG];
    this.B.Dec = decB;
    this.B.Hex = DecToHexTable[decB];
}

Color.prototype.ToHex = function()
{
    return "#" + this.R.Hex + this.G.Hex + this.B.Hex;
}

今回作ったクラスはPaletteとColorの二つ。Paletteは10進数のDecと16進数のHexを持つ、ただそれだけのもの。クラス名がPaletteで良いのかどうかが限りなく微妙で悩ましいです。ていうかかなりダメ。

ColorはR,G,Bという3つの独立したPaletteを持つクラスです。まずコンストラクタは、文字列でくる色表現をPaletteに作り替えます。で、まあ、分岐が3つほど、1つ目は、"aliceblue"とか、色名でやって来た場合にColorTableを使って16進表現に変換。2つ目は、rgb(10,10,10)とか、10進でくる(IE以外)色表現を分解します。substringの決め打ちですね。だせえ。3つ目は、16進でくる(IE)色表現をやっぱりsubstringの決め打ちで分解。なお、片割れはみたとおり、DecToHexTable,HexToDecTableのプロパティで直接振ってます。

SetFromDec関数は10進できた色からセット、ってまんまですがまんまです。ToHexは16進で表した文字列で出力。そのまんまですがそのまんまです。

// Method

function FadeText(elem, color, targetColor, intervalID)
{
    clearInterval(intervalID[0]);
    var fadeR = (targetColor.R.Dec - color.R.Dec) / (fadeSpeed / resolution);
    var fadeG = (targetColor.G.Dec - color.G.Dec) / (fadeSpeed / resolution);
    var fadeB = (targetColor.B.Dec - color.B.Dec) / (fadeSpeed / resolution);
    var r = color.R.Dec;
    var g = color.G.Dec;
    var b = color.B.Dec;

    intervalID[0] = setInterval(function()
    {
        r += fadeR;
        g += fadeG;
        b += fadeB;
        if ((fadeR > 0) ? r >= targetColor.R.Dec : r <= targetColor.R.Dec) r = targetColor.R.Dec;
        if ((fadeG > 0) ? g >= targetColor.G.Dec : g <= targetColor.G.Dec) g = targetColor.G.Dec;
        if ((fadeB > 0) ? b >= targetColor.B.Dec : b <= targetColor.B.Dec) b = targetColor.B.Dec;
        color.SetFromDec(Math.floor(r), Math.floor(g), Math.floor(b));
        elem.style.color = color.ToHex();
        if (r === targetColor.R.Dec && g === targetColor.G.Dec && b === targetColor.B.Dec)
        {
            clearInterval(intervalID[0]);
        }
    }, resolution);
}

実働するメソッドはこれだけー。この辺もう全然linq.js関係ないし、私のJavaScriptスキルっていうかプログラミングスキルの微妙さが浮き彫りになるだけの代物なので、是非Disって欲しい。そもそもHexって使ってないから必要ないぢゃん、というのには今気付いた。

function AttachEvent(elem, event, func)
{
    if (elem.addEventListener) elem.addEventListener(event, func, false);
    else if (elem.attachEvent) elem.attachEvent("on" + event, func);
}

function Initialize()
{
    var nodeList = document.getElementsByTagName("a");

    E.From(nodeList).ForEach(function(elem)
    {
        var strColor = (elem.currentStyle)
                ? elem.currentStyle.color
                : document.defaultView.getComputedStyle(elem, null).getPropertyValue("color");
        var color = new Color(strColor);
        var intervalID = []; // call by reference
        AttachEvent(elem, "mouseover", function() { FadeText(elem, color, targetColor, intervalID) });
        AttachEvent(elem, "mouseout", function() { FadeText(elem, color, new Color(strColor), intervalID) });
    });
}

// Main

AttachEvent(window, "load", Initialize);

これで全部。登録部分です。AttachEventは、一番手抜きな形式ということで。Initializeは、aタグに対してイベント登録してます。ここは少しだけlinq.js使ってます、ForEach便利、ForEach最高。SetIntervalで関数の引数は、こんなののためだけに無名関数を使うのはどうなんでしょうか、誰かベストな方法を教えてください。SetInterval用のIDも、参照渡しのためだけに配列にしてるのが、どーにもこーにもいかんですねえ。ていうかSetIntervalのIDってどうやって管理するのがベストなのかさっぱり分かりません。

最後に

ライセンスはMs-PL、つまり好きに使って好きに改造して好きに配布していいですよライセンスにします。このスクリプトはlinq.jsの次回更新時に同梱するつもりですが、今すぐ使いたいという奇特な人は、このサイトで使ってるものを使ってやってください。先頭のtargetColorの指定を自分のサイトにあうように変えて、linq.jsの後に読み込んでください。linqfaderっていうのは、勿論linkとlinqをかけてるわけです。動作確認はWindows VistaのIE7, Firefox3, Chromeで行いました。

linq.js ver 1.0.0.1

更新内容

Viewsがあまりにも右肩下がりに、ゼロに届きそうな勢いなので追加燃料投下。焼け石に水、とも言う。Linq.Enumerableに「Choice, RangeTo, RangeDownTo」を追加しました。また、Cycleの引数に配列を入れることができるようになりました。そして、あまり、というか全く突っ込みがこなかったのでStableにしちゃいました。ああ、私もDisられたい。キャッキャウフフの夢は遠い。さて、で、以下に追加メソッドのサンプルを記します。

Choice

引数のものをランダムに、無限に繰り返し生成します。つまりCycleのランダム版です。

var TrueOrFalse = function(){return E.Choice(true,false).First()};
TrueOrFalse(); // true or false

例では、無限に生成される「True,False」の、先頭を取得することでTrueかFalseかがランダムに選択されます。関数でくるんでいるのは、var TrueOrFalse = E.Choice(true,false);で止めておいて、使う時はTrueOrFalse.First()にすることと同じなのですが、見栄えがいいかな、と、その程度の理由なのでどうでもいい話です。

var array = E.Range(1,6).ToArray();
var Dice = function(){return E.Choice(array).First()};
Dice(); // 1 or 2 or 3 or...6

もう一つ例として、Choiceでは配列を指定することもできます(Cycleも指定できるようになりました)。これでサイコロの出来あがり。サイコロ二つの値ならDice() + Dice()です。非常にお手軽。ただ、複数個なら……

E.Choice(E.Range(1, 6).ToArray()).Take(2).Sum();

Take + Sumを用いることで、大量の個数にも対応できるようになります。更に一つ、Choiceではないのですがサイコロ繋がりで例を出すと……

E.Repeat(E.Range(1, 6).ToArray(), 5).Flatten().Shuffle().ToArray()

ボードゲームに使う乱数は、ダイスを使った乱数(完全なランダム)と、カードを使った乱数(偏りのないランダム)がありますよね。いや、私はボードゲーム全然知らないのですけど、XBLAのCatanにそういうオプションがあったので。カードを使う、例えばトランプの1-13をダイス代わりに使うとすると(使ったカードは場に捨てる) 13x4回の間に必ず全てのカードが4回使われることになります。偏りがなくなるので有利不利が運に左右されにくくなり、またカード終番になると出目が予測できるので取り得る戦法も変わってきます。

というわけで、上記コードは6x5回分のサイコロの出目カードを作成。シャッフルする前にFlatten()で配列状態で送られてくる値を平らにしてやります。あとは、array.pop()あたりで値を取り除きながら取得して、空っぽになったら再生成することでカード乱数の出来あがり。

RangeTo / RangeDownTo

RangeTo(start,to)は「第一引数の値から第二引数の値まで生成」します。通常のRange(start,count)とはまた違った使い道があるかと思います。こちらのほうが素直に記述出来るケースがあったりと、使い分け、ですね。

E.RangeTo('a'.charCodeAt(0), 'z'.charCodeAt(0))
    .Select("String.fromCharCode($)").ToString();

結果は「abcdefghijklmnopqrstuvwxyz」という文字列。これだけじゃつまらないので、もう少しひねくれてみると……

var numbers = E.RangeTo(0, 9);
var alphabet = E.RangeTo('a'.charCodeAt(0), 'f'.charCodeAt(0)).Select("String.fromCharCode($)");
var digits = numbers.Concat(alphabet); // 0-9とa-fを連結する
// 直積で0-fの二桁全ての組み合わせを出す(00からff、256個)
var colors = E.From(digits).SelectMany(function(i)
{
    return E.From(digits).Select(function(j)
    {
        return i.toString() + j.toString();
    })
});
// R, G, Bにそれぞれ独立して割り振る
var R = colors.ToArray();
var G = colors.ToArray();
var B = colors.ToArray();

これで00からffまでの色コード配列がR,G,Bの3つ分できました。普通に書くと、それなりに長くなるんじゃないかと思えるので、こうして(分かりやすいかはともかくとして)サクッと記述出来るのは魅力的です。自分で言うなって話ですが、いや、LINQ自体が、ね。知れば知るほどC#のLINQは良く出来てるな―、と思います。上のコードはC#でも当然動かせます。ただ、C#は型が異なるものをConcatできないので、numbersの生成時にSelectでString型に変換、alphabetの生成時にint->char->stringに変換する必要がありますけど。

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var clone = E.From(array).ToArray();

そういえば、前回RepeatがいわゆるFillの代用になると書きましたが、From().ToArray()はシャローコピーの代用になります。あんまり派手なことに使わずに、地味にこそこそっと使うのがお薦め。派手にメソッドチェーン組み立てるのもお薦め。楽しいです、ほんと。

linq.js サンプルサンプル

前回投稿したlinq.js、誰の目にも触れず埋もれてしまうのではないかと思っていたのですが、ある程度は見てもらえたようでホッとしました。表作ったり動画作ったりと、ゲーム攻略サイトのノリで書き上げたかいがありました(そして、もうすっかりゲームサイトじゃなくなってしまった!)。ここ数日に新しく登録されたCodePlexのプロジェクトで見ても、Views/Downloads共に多いほうのようです。とはいってもDLは30前後なので、感覚的には全然少ないのですが。ただ、そもそもJavaScriptで野良ライブラリを使うシチュエーションなんてないだろ常識的に考えて……といったところなのでしょうがない。でも使って欲すぃ。前回は完全にチュートリアルだったので、今回はちょっと実用っぽいサンプルで行きます。

// abcdefghijklmnopqrstuvwxyzという文字列を作る
E.Range('a'.charCodeAt(0),26)
  .Select("String.fromCharCode($)")
  .ToString()
// 現在の年から10年前までを生成(2009,2008,2007...,2000)
E.RangeDown(new Date().getFullYear(),10)
// 0で初期化した配列、とか
// 空配列で初期化した配列、とかに地味に便利(いわゆるFill)
var array = E.Repeat(0, 100).ToArray();
var array = E.Repeat([], 100).ToArray();

RepeatはC#ではイマイチ使い道が見つからないんですが、JavaScriptでは激しく便利なよう。あ、この辺はLINQ Padで動かしながら作れます。a-zの生成は頭の中で作ってたモノはちょっと違ってて反応みながら即座に直してました。やっぱリアルタイムで反映されると便利。特にJavaScriptはIntelliSenseがないので、細かいミスが多くなってしまうので(charCodeFromだと思ってたんですよー)

そういえば、a-zの生成の26という数字は良くない。'z'.charCodeAt(0)を使いたいのですが、Rangeの二つ目はcountなので、(z-a+1)を書かなくてはいけなくて、少し長くなるかなあ。「第二引数の数字まで生成」も用意しておくべきでした。近いうちに追加しておきます。E.ToInfinity('a'.charCodeAt(0)).TakeWhile("$<='z'.charCodeAt(0)")で結果は同じになりますけど、無駄があるかなあ。TakeWhileは加工した後のものに使いたいのであって、生成のすぐ後に使うのは違和感がある。

E.Repeat(null)
  .Select("Math.floor(Math.random() * 26 + 'a'.charCodeAt(0))")
  .Select("String.fromCharCode($)")
  .Select("Math.round(Math.random()) ? $.toUpperCase() : $ ")
  .Take(100)

更に応用して、100個ランダムなa-z,A-Zのアルファベットを生成する、というもの。Selectを繋げていっての加工は楽しくて簡単。生成後はToArray()で配列にするのも、ToString()で文字列にするのも、好きに選べます。終了条件を今回はTake(100)にしましたが、TakeWhileで条件をつけるのが自然。たとえば画面を埋め尽くしたら終了とか。

var rand = new Random();
Func<bool> TrueOrFalse = () => (rand.Next(2) == 0);

Enumerable.Repeat(rand, 100)
  .Select(r => r.Next('A', 'Z' + 1))
  .Select(i => TrueOrFalse() ? (char)i : (char)(i + 'a' - 'A'));

文字を数字と見なしての生成はC#のLINQでも便利に使えます。C#だとこんな感じでしょうか。randomの扱いがちょっと微妙かな? Repeat内にnew Random()としたいところなのですが、それだと二個目のSelect時にはRandomが消滅しているので使えないので、しょうがなく。

Enumerable.Repeat(new Random(), 100)
  .Select(r => new { r, i = r.Next('A', 'Z' + 1) })
  .Select(t => (t.r.Next(2) == 0) ? (char)t.i : (char)(t.i + 'a' - 'A'));

最初のSelectの段階でランダムも保持しておけば解決。ただ、素直に外出しのほうがいいかなあ、という気もします。この辺、どっちにすべきか微妙に悩ましい。この程度の場合ならSelectを二段にせず、一段階目で全部処理してしまえば万事解決な気はします。個人的には多段階に繋いでこそのLINQであり浪漫だと思うのだけど……。

他に、例えばしつこくランダムソートを検証すると……

var result = E.Repeat(0, 10).ToArray(); // 結果格納用配列の初期化
for (var i = 0; i < 10000; i++){
	var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // 配列はあえて手書き?
	array.sort(function() { return Math.round(Math.random()) ? 1 : -1 });
	result[array[0]]++;
}
// 画面に出力
E.From(result).ForEach(function(value, index){
	document.write(index + ':' + value);
	document.write("<br />");
});

/* 結果例
0:498
1:888
2:808
3:1345
4:1033
5:1119
6:897
7:1214
8:1170
9:1028
*/

初期化と書き出しにだけlinq.jsを使いました。比較関数にランダムはダメ・絶対。と、こんな感じに、地味にコソコソっと普通のJavaScriptコードに混ぜ込んでも便利に使えるんじゃないかなあ、と思います。私自身は、もうこれなしだとJavaScript書く気になれない、のですがそもそも全然JavaScriptで何も書いちゃあいないので何とも言えなかったり。

とりあえず、あと一回分はアップデートネタがあるので、今週末にでも追加します。

linq.js - JavaScript用LINQライブラリ

概要

JavaScriptでC#のLinq to Objectsを実現するライブラリ。

// C# LINQ
Enumerable.Range(1, 10).Where(i => i % 3 == 0).Select(i => i * 10);
// JavaScript + linq.js 1
E.Range(1, 10)
  .Where(function(i) { return i % 3 == 0; })
  .Select(function(i) { return i * 10; });
// JavaScript + linq.js 2
E.Range(1, 10).Where("i=>i%3==0").Select("i=>i*10");
// JavaScript + linq.js 3
E.Range(1, 10).Where("$%3==0").Select("$*10");

こんな風に記述できます。この例は1から10の整数を発生させ、そのうち3で割り切れる数を10倍にするというもので、結果は30,60,90になります。Linq to Objectsは、強力なリスト操作ライブラリ群とでも思えば大体あってる、かな?C#とは縁のない人でも、便利に使えると思います。

LINQに対するアプローチは色々あるようですが、私はド直球にメソッド構文を実現することを目指しました。LINQの特徴として多く紹介されているクエリ構文を実現しようとすると、ただ煩雑になるだけで実用的なメリットはないからです。あくまで実用性を第一に考えました。そのため、無名関数をラムダ式風の文字列で記述可能にし、また、幾つかの短縮記法を設けてあります。

JavaScripterのための簡易説明

index付きのforeachが書け、IEでmapやfilterやreduceが使える。非破壊的なsortやremoveができる。DOMに対しても同じようにforeachやmapを適用できる。shuffle(ランダムに並びかえ)やdistinct(重複除去)など、便利メソッド集として使える。また、メソッドチェイン形式でシーケンスを加工していくことができる。具体的な用法は下の方で書くチュートリアルや即時実行可能な例題とセットのlinq.js Referenceを参考にしてください。

C#erのための簡易説明

文字列によるラムダ式の記述では、複数引数時でもカッコは必要ありません(つけても問題ありません)。引数がゼロ個の場合は、C#ではダミーとして「_」を使ったりしますが、linq.jsの場合は=>も書く必要がありません。例えばRepeatは「E.Repeat(null, 1000).Select("Math.random()")」などと記述することで、for文の代用として動かせたりします。また、ラムダ式には幾つかの省略記法を用意してありますが、それは後述します。

匿名型、的なものは、JavaScriptの文法に則りオブジェクトを作ることで解決します。つまりnewは必要なく{key:value,key:value}と書くことで匿名型と同様の動きをします。但しキーの省略は出来ません。また、ToDictionaryはありません、かわりにToObjectがあります。違いは、重複したキーが挿入されても例外を吐かず、上書きされます。もう一つ、基本的にメソッドは例外を吐きません。例えばFirst(predicate)で何も見つからなかった場合は例外ではなくnullを返します。唯一例外を吐くメソッドはSingle/SingleOrDefaultだけです。

Cast,OfType以外のメソッドは全て実装してあり、また、EqualityComparerを除けばオーバーロードも標準通りに従っています。実行時動作もC#と同じ、はずです(違う挙動を示す場合は教えてください)。標準メソッド以外に、「Cycle, From, RangeDown, ToInfinity, ToNegativeInfinity, Flatten, Pairwise, Scan, Slice, ZipWith, Shuffle, ToJSON, ToString, ToTable, Do, ForEach, Write, WriteLine, Force, Trace, TraceF」といったメソッドが定義されています。また、容易に新しいメソッドを拡張メソッドのように追加することが出来ます。

チュートリアル1. 生成

linq.jsを読み込むとLinqという名前とEという名前を占有します。EはLinq.Enumerableのショートカットになっています(jQueryなどの$と同じように、よく使うものなので短縮名を付けてあります)。linq.jsの動作は全てLinq.Objectの連鎖で実現されますが、そのLinq.Objectの生成をLinq.Enumerable以下のメソッドが担当します。生成メソッドは色々あるのですが、詳しくはリファレンスを参照ください、ということでここでは最も使うFromとRangeの解説をします。

var sequence = E.Range(1,10);
var array = sequence.ToArray(); // [1,2,3,4,5,6,7,8,9,10]
var sum = sequence.Sum(); // 55

Range(start,count)はstartの値からcountの個数分だけ整数を生成します。この例では1から10個分、10までです。E.Range(5,2)とすれば、5から2つ分で5,6が生成されることになります。Rangeメソッドが返すものは配列ではなくLinq.Objectなのでメソッドを繋げていくことが出来ます。また、宣言段階では値の生成は始まらず、ToArray(配列に変換)など具体的なオブジェクトへの変換メソッドや、Sumなどの数値集計メソッドが実行される時に値の生成が始まります。このことは以降、SelectやWhereメソッドの解説をする時にまた詳しく解説します。

var array = ["aaa",function(){return 3},{hoge:"tako"},1]; // 関数は除去されます
var seq1 = E.From(array);
var obj = {a:312,b:function(){return "hoge"},c:931}; // キーは.Key、値は.Valueで取り出す
var seq2 = E.From(obj);
var seq3 = E.From(10); // 数字、文字列は生成数1のLinq.Objectを生成します
var seq4 = E.From("hoge"); // つまりE.Repeat("hoge",1)と等しいです

配列やオブジェクトからLinq.Objectを生成するにはFromを使います。配列の場合は直感的に分かりやすくそのまんま順番通りに値を列挙していくのです、が、関数は自動で除去されます。つまりseq1は"aaa",{hoge:"tako"},1という値が生成されることになります。

オブジェクト/連想配列を入れると特殊な動作が入ります。値が関数のものは除去される、というのは配列と同じなのですが、プロパティをKeyに、値をValueに格納したオブジェクトが列挙されます。つまりseq2は{Key:a,Value:312},{Key:c,Value:931}という値が生成されることになります。なお、この動作はC#のDictionary(型付き連想配列)の動作(KeyValuePairが列挙される)を模しています。

Fromには数字や文字列を入れることも可能です。その場合は値を一度だけ送る、つまりRepeat(value,1)と等しくなります。また、オブジェクトに対してLINQを使いたいけれどKeyValuePairに変換したくない、という時はRepeat(obj,1)とすることで回避出来ます。Repeatの詳しい使い方はリファレンスを参照してください。C#標準と違い、回数指定を省くと無限リピートになります。

チュートリアル2. 射影とラムダ式

シーケンスの中身に関数を適用して変形する。他の言語ではmap関数として定義されていることが多いっぽいのですが、LINQではSelectメソッドがそれです。恐らく最も多用するメソッド。

E.Range(1,10).Select(function(i){ return i * 10})

1から10を生成→その数値を10倍。ということで結果は10,20,...,100です。勿論、文字列に変形させることも、オブジェクトに変形させることもできます。引数は、関数です。LINQはSelectに限らず無名関数を多用します、が、JavaScriptではfunction(){return}と書かなければならなくて記述が面倒くさい。ということで、文字列でC#のラムダ式のようなものを記述出来るようにしました。

// 普通の無名関数を用いる
E.Range(1, 10).Select(function(value, index) { return index + ':' + value });
// ラムダ式風の記述 "引数 => 式" として記述する
E.Range(1, 10).Select("value,index=>index+':'+value");
// 引数が一つの場合は、記号$を引数の変数として用いることができる
E.Range(1, 10).Select("i=>i*2");
E.Range(1, 10).Select("$*2");
// "x=>x"のような、自分自身を返す関数は使いたい場合、""で省略できる
E.Range(1, 10).Join(E.Range(8, 5), "x=>x", "x=>x", "outer,inner=>outer*inner");
E.Range(1, 10).Join(E.Range(8, 5), "", "", "outer,inner=>outer*inner");

ラムダ式は、=>の左が引数、右が式として評価されます。引数が一つの場合、=>を省くと$がその引数のかわりになります。プロパティへのアクセスも普通の引数を使うときと同じように「"$.hoge"」などでアクセスできます。更にJoinなど、"x=>x"を書く必要がある場合のための省略記法として""を用意してあります。つまり、""は"$"に等しく"x=>x"に等しくfunction(x){return x}に等しいことになります。

なお、ラムダ式をネストする際、前のラムダ式の変数は参照出来ません。何のこっちゃって感じですが、同梱のsample.htmの一番下のコードを見てください。その場合、無名関数を用いることで参照することができます。

チュートリアル3. 抽出・テスト

フィルタリングもSelectと同様に多用するメソッドです。他の言語ではfilterとして定義されていることが多いっぽいですが、LINQではWhereメソッドがそれです。

E.Range(1,10).Trace("Gen:")
  .Where("$%3==0").Trace("Filtered:")
  .Select("$*10")

1-10のうち3で割り切れる数のみを通して、10倍する。30,60,90が結果。で、挟んでいるTraceとは何かというと、そこを通っている値を画面に出力します(document.writeで書き出しているので、あまり実用性はありません、TraceFメソッドを用いるとFirebugのコンソールに出力できます)。リファレンスに設置してあるLINQ Padで試してみて欲しいのですが、Gen[1,2,3] -> Filtered[3] -> 30といった感じに値が流れている様子が確認出来ます。あくまで、E.Range(1,10)は10個の値を全て生成してからWhereに渡しているのではなく、一つ一つ生成して終わりまで通しています。

E.ToInfinity().Where("$*$*Math.PI>10000").First()

例えば、面積が10000を超える最初の半径は幾つ?といったように、どこまで値を生成すればいいのか分からない問題に効果を発揮します。なお、無限生成系は条件指定によっては永遠に止まらないものが出来てしまいますので注意してください。また、First(最初を取得)やTake(個数を指定して取得)は止まりますが、Last(最後のものを取得)やSingle(唯一の要素を取得)は絶対に止まりません。「最後」を得るために、「唯一」であることを確認するために生成が終わるまでループを回し続けるからです。

応用

以上で基本は終了です。同梱してあるsample.htmには、ひねくれた実例なども収録してあるのでご参照ください。また、リファレンスの上部に置いてあるLINQ Padはリアルタイムに動作を確認することが出来ますので、R.Range(0,10)辺りで数字を生成して遊んでみてください。例えば動画の例はランダムソート(笑)の検証と同じものになっています。0-10を生成してシャッフルして先頭を取り出したものを→キーを自身・値を自身にしてグループ分けして集計のキーをkey・集計の個数をcountというプロパティに格納したオブジェクトに変換して→keyの順番で昇順にソートして→書きだす。というコードです。

その他

C#のLINQの記述方法とほとんど同じなので、MSDNのLINQ to Objectsに関するドキュメントが参考になるかと思います。例えば標準クエリ演算子の概要とか。ネット上のLINQ to Objects用コードもそのまま動かせるはず、です。

実行効率……考えたら負けかな、と思っている。そんな派手なことやらなければ誤差範囲に収まる、といいなあ。この程度はGoogle Chrome先生が何とかしてくれる!あと、LINQ愛に突き動かされて書いたけれど、私はJavaScriptド素人なので(今までJavaScriptでまともな何かを書いたことがない) ソースへ、厳しい突っ込みが貰えたら嬉しいなあ、と思っています。内心、こういうコードでいいのか不安でいっぱいなので。

C#って日本のネット上では地味な存在だけど、linq.jsが架け橋になって、C#にも興味を持ってもらえたらなあ、なんて思いつつ、とりあえず、試してみてもらえると嬉しいです。

LINQPad for JavaScript

LINQPadのように気楽に入力して動作確認。JavaScript上で動かせると超お手軽で、確認が楽ちんでいいですね。LINQも正規表現のようにリスト操作用DSLと考えてみれば、こうやって即時評価できるチェッカーの便利さも納得。個人的にはこれだけでも作って良かったな、と思えています。

JavaScriptで一番使う列挙可能な物体といったらDOMなので、ちゃんとNodeListも扱えるようにしました。LINQPad上でもプロパティとキーを画面に出したりして動かせます。コード例は、outerHTMLがタグを吐くせいで、この画面上だとエラー出るのでWhereで除外しました。フィルタリングもできるよー、の例ということで。

現在、引数指定無し時の引数識別子は「x」ということにしているのだけど、$のほうが自然かなあ。どう思いますか? 変えるなら今のうちというか今を逃したら変えられないというか。$を使うとjQueryと衝突しそうなイメージが出るような気がして若干気が引ける(実際は全くそんなこたぁありません)。でもまあ、逆にメソッドチェーンだらけだし、見た目がjQueryっぽくなって受け入れられやすくなる、気もする。

※ DL数一桁で終わるであろう現実を見ていません

現在の作業状況としては、リファレンス書きが終わって、あとは英語でチュートリアル書くだけなんですが英語書けないので諦めようかと思案中。別にCodeplexだからって英語で書かなくてもいいぢゃない。諦めるを選べば、今週中に公開出来そうです。諦めないを選べば永遠に公開出来なさそうです。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive