Archive - JavaScript

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)のネタ元はここだったりして。最高にクール!

JavaScriptでString.Format的な超簡易テンプレート置換

// String.Format的な超簡易テンプレート置換関数
var Format = function(template, replacement)
{
    if (typeof replacement != "object") // 可変長引数時はreplacementを詰め替え
    {
        replacement = Array.prototype.slice.call(arguments, 1);
    }
    return template.replace(/\{(.+?)\}/g, function(m, c)
    {
        return (replacement[c] != null) ? replacement[c] : m
    });
}
 
// 例。可変長引数渡しでも配列渡しでもオブジェクト渡しでも可。
var case1 = Format("<div id={0}>{1}</div>", "あいでぃ", "要素");
var case2 = Format("<div id={0}>{1}</div>", ["あいでぃ", "要素"]);
var case3 = Format("<div id={ID}>{ELEM}</div>", { ID: "あいでぃ", ELEM: "要素" })

.NET FrameworkのString.Formatは文字を連結するのに、非常にお手軽で良いです。というわけでJavaScriptでもそれをやる。ついでにテンプレート置換風にオブジェクト渡しにも対応させる。単純な置換時は数字で、長ったらしい置換時はオブジェクトで。両方に対応させなければ、詰め替えが必要ないので正規表現でカカッと一行なんですねえ。詰め替えも別にslice.callで一発だし。以前にlinq.jsを絡めてgdgdとやってたのですが、二度もね!、あんなにgdgdやらずとも、もんのすごーく単純明快に書ける。無理やり使おうとして、無駄に複雑になるのはイクない。

と、恥ずかしくなったので今回載せました。あと、JavaScriptは文字連結面倒くせー、って時にササッとコピペで取り出して使いたい時のために(笑) ちゃんとDateTime等も含めたフォーマット変換に対応させるとか、テンプレートだったらちゃんとテンプレートエンジンな感じでforやifも動くように、とかの話は知りません。

そういえば、置換部分の関数ですけど、最初は格好つけて「return replacement[c] || m」って書いたんですが、これだとマッチがハッシュ内に見つからなかった場合(undefinedになってる)だけでなく、 空文字列の場合もfalse扱いになってしまってダメなんですね。C#の??のように使いたいのですが、例えば数字だと「var i = 0 || 3」だったら3になるしで使いづらい。というわけで、結局==nullばかり使うことになる。===undefinedって書けって話でもありますが、まあ、==nullのほうが色々考えなくて済むから楽で。

無限リピートの幸福

Reactive Frameworkが、結構に無限リピートな感じなので、関連してC# Linqでどう書くにあった13日金曜日問題を今更書いてみた。n番煎じ。

// 今日から2013年12月31日までの、13日の金曜日とその総数を表示してください。
// 「今日」を無限リピートという方針で書いてみたりして(総数は省略)
// 利点はTodayを変数として外側に定義する必要が無くLinq内に閉じ込められる
// Toを求めるのに足したり引いたりする必要がなく自然に書ける、の二つかしらん
// 「まで」という問いに対してTakeWhileで解答するのは自然で良いと思う
 
Enumerable.Repeat(DateTime.Now, int.MaxValue)
    .Select((d, i) => d.AddDays(i))
    .TakeWhile(d => d.Year < 2014)
    .Where(d => (d.DayOfWeek == DayOfWeek.Friday) && (d.Day == 13))
    .ToList()
    .ForEach(d => Console.WriteLine(d.ToShortDateString()));

TakeWhileが好きです。問題文に対して、自然に解答出来るような気がするので。「今日から(Repeat)」「2013年12月31日までの(TakeWhile)」「13日の金曜日(Where)」。実に自然に記述できる。いやまあ、Repeatが直感的かというと結構微妙なところではありますが。Rangeでfrom,toのほうが自然だろ常識的に考えて、というのも確かなんですけど、Rangeだとtoを作るのに計算式が必要ってのが、ちょっと違うかな、と。

Pizza (programming language)のexampleにもあるような、Streamを始めとして何かを無限リピートしてTakeWhileで終了条件を設定、というのはパターンとして結構幅広く使える、と思う。ある種のデザインパターン。イディオムイディオム。参考リンクはC# 3.0 と while(true) と Iterator - NyaRuRuの日記この辺り。

例えばVS2010から搭載されるEnumerable.Zipや、あとCycleを定義してみる。

// この二つを混ぜ合わす(VS2010で搭載されるZip関数)
var seq1 = Enumerable.Range(1, 10);
var seq2 = Enumerable.Range(10, 10);
Enumerable.Repeat(new { e1 = seq1.GetEnumerator(), e2 = seq2.GetEnumerator() }, int.MaxValue)
    .TakeWhile(t => t.e1.MoveNext() && t.e2.MoveNext())
    .Select(t => t.e1.Current + t.e2.Current); // ここがZipのSelectorの部分
// foo,bar,hoge,foo,bar,hogeを無限に繰り返す
var elements = new[] { "foo", "bar", "hoge" };
var cycle = Enumerable.Repeat(elements, int.MaxValue).SelectMany(ar => ar);

Linqのお陰でかつてない勢いでint.MaxValueを使っているこの頃。Repeatも万能ですねえ。いやまあ、もう素直にAchiral使えよって話なんですが、標準メソッドのみで粘るのも面白くて。そういえばでついでなのでlinq.jsでもやってみた。

// JavaScriptはAddDaysがないので副作用全開でTodayを
// setHours(24)で翌日にしてしまう、という方針でやってみた
 
E.Repeat(new Date())
 .Do("$.setHours(24)")
 .TakeWhile("$.getFullYear() < 2014")
 .Where("$.getDay() == 5 && $.getDate() == 13")
 .ForEach("alert($)");

DoはReactive Frameworkにもありました。副作用を加えた上で素通しするメソッド。副作用は嫌なものです。汚いです。何が嫌かというと、動作を考えるのに見る範囲を広げなきゃいかんところかなあ。そしてLinqの何がいいかというと、見る範囲が物凄く限定される(ラムダ式一文だけを見ればいい)と思っている。だからLinq内でクロージャ(というか外部の変数をキャプチャして使う)もあんま好ましくないし、C#クエリ構文のletも好きじゃない。なるべくなら使いたくない。長文耐性なのは分かるけれど、カッコやインデントがなくてスマートだけれど、その分だけスコープが不明瞭になるという側面が否めない。まあ、letが必要なシチュエーションをSelectManyでやると、大抵はもっと奇怪になるのですけど。

んでまあ、この場合だとAddDaysのかわりにnew Date(year,month,day)で新しいのを作れば副作用なくSelectが使えるわけですが、ありきたりで面白くないと思ったので別な方向に走ってみた。というか、無限リピートは、無限リピートする何かに対して副作用全開で操作を加え続ける、という形の方が面白いというか実用的というか普通だとは思う。冒頭の例みたいなやつだと、別にRangeでよくね?って感じですし。Haskellじゃないんだから、潔癖症にならずに、副作用といかに楽しくお付き合いするかが大事なのですかね。

あ、ちなみに$は引数が一つの場合の省略記法です。こういった機能はScalaにもある。引数が一つのみの場合が大半なので、記述がグッと縮まるし、何よりも引数名を付ける必要がないのが嬉しい。C#でも使えるようになると嬉しいなあ、とずっと思ってるんですが中々どうして無理なんですかねえ、残念。

JavaScriptで要素追加するやり方

ド素人がjQueryとprototype.jsではどう書くのかな、と思っただけです。メジャーな両者ですが実はまともに使ったことがないのです。困ったことに。しょうがないので見よう見まねで書く。

<!-- このselectにoptionを一個追加する -->
 
<select id="selectID">
    <option value="1">hugahuga</option>
</select>
 
<script type="text/javascript">
 
    // 素のJavaScriptその1(古臭いというか微妙な……)
    var option = new Option("要素", "属性");
    var select = document.getElementById("selectID");
    select.options[select.options.length] = option;
 
    // 素のJavaScriptその2(これはダルい)
    var option = document.createElement("option");
    option.setAttribute("value", "属性");
    option.appendChild(document.createTextNode("要素"));
    document.getElementById("selectID").appendChild(option);
 
    // みんな大好きjQuery
    $("<option>").attr({ value: "属性" }).text("要素").appendTo("#selectID");
 
    // 何だかんだで好きなprototype.js
    var option = new Element("option", { value: "属性" }).update("要素");
    $("selectID").insert(option);
 
    // linq.js + linq.xml.jsの関数型構築
    var option = X.Elem("option", X.Attr("value", "属性"), "要素");
    X.ID("selectID").Add(option);
 
</script>

素のJavaScriptその1はねーよ、というわけで、その2をいかにスマートにやるかという話。だと思う。jQueryのappendToが合理的というか便利なのは分かるけど、キモく感じてしまう。んで、どれが好きかっていたら、当然自分で作ってるlinq.jsのが一番好きですよ(笑)

// linq.js + linq.xml.js
var options = E.RangeTo(1, 12).Select(function(i)
{
    return X.Elem("option", X.Attr("value", i), i + "月");
});
X.ID("selectID").Add(options);
 
// prototype.js
var options = $R(1, 12, false).map(function(i)
{
    return new Element("option", { value: i }).update(i + "月");
});
var elem = $("selectID");
options.each(function(e) { elem.insert(e) });

X.Elem()もAdd()もLinqオブジェクト/可変長配列を受け取れるので、まとめてドバーっと追加が結構楽かな、と思います。eachとかじゃなく、そのまんま追加出来るってのが大事。上の例だと、prototype.jsではmapでoptionsを作らずそのまんまeachでinsertしちゃえばいいぢゃん、というのはそのとーりなんですが(2回もループ回ることになるしね、あ、linq.jsのは遅延評価しているのでループはAddで呼び出される時の1回しか回りません)、配列(的なもの)が既にある状態ってのは、結構ありますよね?

と、何故か突然アピールしてますがlinq.xml.jsは作りかけで放置しているので足りない関数がいっぱいあるんですけどね!

WSH用にCOMのIntelliSenseを自動生成する

本題、の前にJavaScript用のIntelliSenseの作成方法について。以前、linq.jsにIntelliSense用ファイルを追加した時に利用法を書きましたが、今回はvsdocの作成方法を、ざっと書きます。まずVS2008 SP1とパッチを適用する。hoge.jsに対しhoge-vsdoc.jsを用意するとhoge.jsのかわりにhoge-vsdoc.jsがIntelliSense用に読み込まれる。IntelliSenseでVSが見ているのはメソッド名と引数だけなので、コードの中身はなくてもいい。例えば本来は存在するがprivateメソッドのつもりのものは、vsdoc.js側に書かなければIntelliSenseには表示されない。C#と同様に関数にはXMLドキュメントコメントを記述することが出来る。記述箇所はC#とは異なり開始の波括弧の直下。ドキュメントコメントのタグで現在機能しているものはsummary, params, returnsのみ。特に重要なのはreturns typeで、これにprototypeが存在する関数(ようするにクラスですなー)を指定することで戻り値の型をVSに認識させ、IntelliSenseを働かせることが出来る。ちなみに、ただのオブジェクトだと認識してくれない。

function Hoge(aaa, bbb)
{
    /// <summary>hogehogehogehoge</summary>
    /// <param name="aaa" type="String">a!a!a!a!a!</param>
    /// <param name="bbb" type="Optional:Boolean">b?b?b?b?b?</param>
    /// <returns type="Number"></returns>
}

基本はこんな感じ。引数の省略や、型が目で見て分かるので大分書きやすくなります。 で、いつぞやかに作成中とか言っていた通りにlinq.jsをWSH対応させようとしているわけですが、IntelliSense書きの量が思ったよりも膨大なわけです。実は最初は普通に手書きしてました。一応勉強も兼ねて書写みたいなノリで。ですが、あまりにも手間かかりすぎるので自動生成することにしました。こんなの人力でやるもんじゃないですよ。微調整はどちらにせよ必要なのですか、大枠だけでも書きだされていると物凄く楽になる。

static void Main(string[] args)
{
    // add reference and replace dll path
    var assembly = Assembly.LoadFrom(@"Interop.IWshRuntimeLibrary.dll");
    var types = assembly.GetTypes();
 
    var enums = types
        .Where(t => t.IsEnum)
        .OrderBy(t => t.Name)
        .Select(t => string.Format("{0}: \r\n{{\r\n{1}\r\n}}",
            t.Name,
            Enum.GetNames(t).Select(s => string.Format("\t{0}: {1}", s, (int)Enum.Parse(t, s))).Join(",\r\n")));
 
    var classes = types
        .Where(t => t.IsClass)
        .Select(type =>
        {
            var bindingFlag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
 
            var properties = type.GetProperties(bindingFlag)
                .OrderBy(pi => pi.Name)
                // .Select(pi => string.Format("{0}: {1}", pi.Name, pi.PropertyType.Name
                .Select(pi => string.Format("{0}: null", pi.Name));
 
            var methods = type.GetMethods(bindingFlag)
                .Where(mi => !mi.IsSpecialName && mi.Name != "GetEnumerator")
                .Select(mi => new { mi.Name, Parameters = mi.GetParameters(), ReturnType = mi.ReturnType.Name })
                .OrderBy(t => t.Name)
                .Select(t => string.Format("{0}: function({1})\r\n{{\r\n{2}\t/// <returns type=\"{3}\"></returns>\r\n}}",
                    t.Name,
                    t.Parameters.Select(pi => pi.Name).Join(", "),
                    t.Parameters.Select(pi => string.Format("\t/// <param name=\"{0}\" type=\"{1}{2}\"></param>\r\n",
                            pi.Name,
                            (pi.IsOptional) ? "Optional:" : "",
                            pi.ParameterType.Name.Replace("Void", "void").Replace("Int32", "Number")))
                        .Join(""),
                    t.ReturnType.Replace("Void", "void").Replace("Int32", "Number")));
 
            var result = properties.Concat(methods).Join(",\r\n");
            return string.Format("{0} = function() {{ }}\r\n{0}.prototype =\r\n{{\r\n{1}\r\n}}",
                Regex.Replace(type.Name, "Class$", ""),
                result.Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n"));
        });
 
    var name = assembly.GetName().Name.Split('.').Last();
    File.WriteAllText(name + "_enum.js", string.Format("{0}Enum =\r\n{{\r\n{1}\r\n}}", name,
        enums.Join(",\r\n").Split(new string[] { "\r\n" }, StringSplitOptions.None).Select(s => "\t" + s).Join("\r\n")), Encoding.UTF8);
    File.WriteAllText(name + "_class.js", classes.Join("\r\n\r\n"), Encoding.UTF8);
}
 
static string Join<T>(this IEnumerable<T> source, string separator)
{
    var index = 0;
    return source.Aggregate(new StringBuilder(),
            (sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o))
        .ToString();
}

参照設定で対象のCOMを読み込んで、一旦ビルド。生成されてるInterop.hoge.dllを読み込んで解析、という流れをとってみました。もっとマシなやり方があれば教えてください。コードは、んーと、string.Formatがクドくて見難いですね! 最初はVSのフォーマッタに後で手動でかければいいや、と思ってたのを、出力状態でちゃんとフォーマットされてるようにと継ぎ接ぎしてたら酷いことに。一回文字列にしたものを改行でバラして再生成とか笑えない。こういうの、HTML/XMLが対象なら何も考えなくてもLinq to Xmlで綺麗に書けるのになあ……。 それ以外はまぁ、こんなものかしらんって感じでしょうか? リフレクションが絡むとLinq大活躍。Selectがネストしまくるのでクエリ構文の出番はない。リフレクションは掘り進める都合上、ネストが深くなるのでLinqがなかったら、と思うとゾッとします。最近はforの二重ループですらウエエ、とか思ってしまうので。

IWshRuntimeLibraryEnum =
{
    IOMode:
    {
        ForReading: 1,
        ForWriting: 2,
        ForAppending: 8
    }
}
 
Folder = function() { }
Folder.prototype =
{
    Name: null,
    ParentFolder: null,
    Drive: null,
    CreateTextFile: function(FileName, Overwrite, Unicode)
    {
        /// <param name="FileName" type="String"></param>
        /// <param name="Overwrite" type="Optional:Boolean"></param>
        /// <param name="Unicode" type="Optional:Boolean"></param>
        /// <returns type="TextStream"></returns>
    }
}

一部抜粋ですが、こんなようなデータが出力されます。プロパティが全部nullなのは、ここに未指定のものが記述されているとIntelliSenseがエラー起こしてしまうから。例えばDrive: new Drive()で戻り値の型指定が可能といえば可能なのですが、出現位置の上下が問題になってくるので結構面倒くさい。ようはFolderよりも上にDriveがあれば関数が存在するので大丈夫だけど、下にあれば存在しないのでエラー。当たり前といえば当たり前なのですが、ダミーでのIntelliSense作りとしては面倒な問題でして。 この辺は素直に諦めて全部nullで型連鎖を止めてしまうのがお気楽といえばお気楽。あと、Dateはnew Date()にしてもDate.prototypeにしても型指定出来なかったりする問題もある。これは今のところ対処不能。

そんなわけで、linq.js + WSHはわりと順調に作成中なので期待しないでも待っててください。IntelliSenseというだけじゃなく、JScriptではバイト配列が扱いにくいので、扱いやすく出来るような補助メソッドを用意したりとか色々やってたらいつになっても終わらないー。例としてWSHでMD5を作る、とか。.NET Frameworkを通しているのでSHA1でも何でもいけます。これでWSHでwsse認証通してAtomAPIで投稿、とか出来ますね。まあ、もうC#でいいぢゃん、って気がかなりしなくもないですけど、うーん。C#4.0からdynamicが追加されるからCOM連携も楽になるしねえ。ただサイドバーガジェットに使うとか、JavaScriptの用途はまだまだあるもん!(これもSilverlightでやれば?って話がなくもないので、うーん)

linq.js :: Next

音沙汰のないlinq.jsなんですが、現在はWindows Script Host対応を進めています。対応といっても数カ所書き換えるだけなんですけど、それに合わせてWSH用のラッパーライブラリを書いているので、それに時間を取られてる感じです。基本的にはほんと薄いラッパーで、列挙処理がlinq.jsに渡せるのでそっちで好きに処理してね、という方向なので機能面での補助は一切なく別にすぐ出来上がるというかもうほとんど出来てるんですが、IntelliSenseを聞かせるためのvs-doc書きに物凄く時間喰われています。とにかくIntelliSense命な私は、IntelliSenseが動かないものなんて書きたくない!なければ自分でIntelliSenseを書く!という意味不明な方向で頑張ってます。

画像の一枚目は一週間前のものなので、現在はW.ToLinqは廃止して、E.Fromで動くようになってます。何のこっちゃ。

このご時世、WSHなんて下火、これからはPowerShellだよねー、って感じですが、それでも私はWSHで頑張る! WSHでLinq書けるのかゴルァ、を合い言葉に。JavaScript好きだし。まあ、素のJScriptだとEnumeratorを被せなきゃいけなくて列挙処理がゴミで使う気になれないのは確かなのですが、そこをlinq.jsがあれば何とか出来るわけなので、全然WSHは現役で行ける、Windows7時代でも全然行ける、と思います、思いたいです。まあ、あとWindowsサイドバーガジェット(デスクトップガジェット)にも使えるので、もうちっと踏ん張っていきたいな、というところ。Web系で頑張るのは無意味なのでニッチを狙いだしたとかそういうことではありま、す。

linq.js ver 1.3.0.2 - CascadeDepthFirst/BreadthFirst

linq.jsにCascadeDepthFirst, CascadeBreadthFirstを追加しました。ちょっと分かり辛いのですが非常に強力です。深さ優先/幅優先で多段SelectManyをかけていくようなイメージ。説明しづらいので例をどうぞ。

<div id="tree">Root
    <div>Spine
        <div>Neck
            <div>Head</div></div>
        <div>RClavicle
            <div>RUpperArm
                <div>RLowerArm
                    <div>RHand</div></div></div></div>
        <div>LClavicle
            <div>LUpperArm
                <div>LLowerArm
                    <div>Hand</div></div></div></div></div>
    <div>RHip
        <div>RUpperLeg
            <div>RLowerLeg
                <div>RFoot</div></div></div></div>
    <div>LHip
        <div>LUpperLeg
            <div>LLowerLeg
                <div>LFoot</div></div></div></div></div>

こんなHTML、というかツリーに色々と操作することにします。例えばinnerTextのように、深さ不明のchildNodesを全て掘ってTextNodeだけを取り出して値を連結するとしたら、通常は再帰を使いますよね。でも、CascadeDepthFirstを使えば

var root = document.getElementById("tree");
var innerText = E.Make(root)
    .CascadeDepthFirst("$.childNodes")
    .Where("$.nodeType == 3")
    .Select("$.nodeValue")
    .ToString();

こんな感じに書けます。rootから深さ優先探索で子ノードを全取得、そのうちnodeTypeが3のもの(TextNode)のnodeValueを文字列連結。宣言的に、再帰よりも分かりやすく書けます。何より後段で豊富なLinqメソッドを使って値を操作していけるのが利点になります。今回から新しく追加されたMake(hoge)というのはRepeat(hoge,1)と等しい。Fromを用いるとオブジェクトはKeyValuePairに、文字列は一文字ずつのシーケンスに化けてしまうので、今回からこのメソッドを用意しました。

実のところMakeもCascadeDepthFirstもC# 3.0 Supplemental Library: Achiral - NyaRuRuの日記のパクりです(AchiralではMakeはMake.Sequence)。ぶっちゃけ今までも勝手に散々パクッているので(ごめんなさい、ありがとうございます。ライセンス的にアレっぽいので、どこかに書いておかないと……) むしろ何で今更追加なのかというと、あまりにも露骨すぎて劣化コピーを入れるのは失礼に思っていて。結局入れてしまったけれど。そんなわけで応用的なものを一つ。引き続き↑のDOMツリーを使って……

var root = document.getElementById("tree");
E.From(root.childNodes)
    .Select(function(child) { return { child: child, parent: root} }) // 外部の値を取りこむ時は文字列ラムダ式は使えません
    .CascadeDepthFirst(function(pair) // 中を入れ子で親の値を参照したいので文字列ラムダ式は不可
    {
        return E.From(pair.child.childNodes)
            .Select(function(child) { return { child: child, parent: pair.child} });
    }, "v,n=>{value:v,nestLevel:n}") // CascadeDepth/BreadthFirstの第二引数はネストレベルを利用可能
    .Where("$.value.child.nodeType == 1") // ELEMENT_NODEだけを取得するためフィルタ
    .WriteLine(function(t)
    {
        return t.nestLevel + ':'
            + E.From(t.value.child.childNodes).First().nodeValue + ' . '
            + E.From(t.value.parent.childNodes).First().nodeValue;
    });

例もパクりです。与えられた木から,子→親への対応を作る,を C# で - NyaRuRuの日記を、HTMLだったらツリーはDOMだよね、という感じで書いてみました。第二引数にはネストレベルを取得できるリザルトセレクターが使えます(省略も当然可能)。んー、DOMだとTextNodeが混ざるせいで、例として不適切に処理が混沌としてしまいました。あ、WriteLineの部分は子要素のFirstがTextNodeであると決め打ちしてます。正確にやりたいなら冒頭のinnerTextの例のようにWhereでTextNodeのみに絞った上で、ToStringで連結したほうが良いですね。

CascadeDepthFirstをCascadeBreadthFirstに変えると(両者は探索方式が違うだけで引数や戻り値は一緒)、こんな結果になります。

動作の差異がよく分かるんじゃないかなーと思います。

continue/break

ForEachでcontinue/breakを使えるようにしました。

E.Range(1, 100).ForEach(function(i)
{
    if (i % 2 == 0) return true; // continue
    if (i == 7) return false;    // break
    alert(i); // 1,3,5
});

灯台下暗しというか、こういうのって自分が必要になるまで気付かないというか。Linqって基本的に前段階のWhereで絞るからこの辺のものって必要になる機会が少ないんですよね。いや、言い訳ですけど。で、まあ、jQueryと同じでtrueをreturnすればcontinue、falseをreturnすればbreakになります。実装は超単純。

while (enumerator.MoveNext())
{
    if (action(enumerator.Current, index++) === false) break;
}

ActionなのにFuncになってしまった!しまった!と、思わなくもないけど、この辺が後付けでグダグダになるのはしょうがないので気にしないことにしよふ。じゃなくて、基本はActionなのでヨシとしておこう。むしろこの辺はJavaScriptがユルフワなのでC#ルールに(用語を)当てはめようとして無理が出ているだけ。そういえばで、個人的にはreturn;でcontinue、return true;でbreakにしたいなあ、とか思ったり思わなかったりなのだけど、その辺は標準に合わせた方が混乱しなくていいよねー、と思うことにしました。

Suggest(モドき)

どうも、新PC組んだりして(Core i7だしメモリ12GだしIntelのSSD2枚刺しでこれで数年は買い換えないでも前線で闘っていける!と思いたいのだけど、そこら中で微妙にコスト削減で若干残念になっていたり、ExtremeじゃなくてMainstreamなところとか) まあ、それはそれ。で、Windows7 RC1を入れて若干の苦戦を強いられていて環境整えるのに時間かかりそう-、というわけでショボいネタを一つ。

// これが補完候補、ただの文字列配列
var list = ["tokyo", "kanagawa", "tochigi", "saitama", "sapporo", "nagano"];
 
// イベントハンドラは引数を二つ取る、一つは送り元、もう一つはイベント
X.ID("inputBox").AttachEvent("keyup", function(sender, event)
{
    var suggestArea = X.ID("linqsuggest");
    suggestArea.RemoveNodes(); // 子ノード全削除
    if (event.keyCode == 13) // eventはイベント
    {
        sender.value = ""; // 空にするだけ……
        return;
    }
    var text = sender.value; // senderは送信元DOM要素、今回はTextBox
 
    // この4行が本編です、ええ
    suggestArea.Add(X.Elem("tbody",
        E.From(list)
            .Where(function(s) { return s.indexOf(text) != -1; })
            .Select(function(s) { return X.Elem("tr", X.Elem("td", s)) })
    ));
});

入力補完モドきです。見にくいですけど、ソースコードの上にある入力ボックスにgaでkanagawaとnaganoが表示される、とかそんな感じ。エンターキーによる入力には対応してません、ただたんに絞り込んで表示するだけのデモです。んーと、suggestArea.Addからの、たった4行でリストの絞り込みして->DOM作成が出来るね、簡単だね、素晴らしいね、関数型構築とLINQの合わせ技は強力ですよね、だからみんなも使ってね、ということが言いたいそうです。世の中、WhereしてSelectするだけで9割方カバー出来る。気がする。気のせい。

ちゃんと真面目に作っていきたい気もするのですが、この強引リニアサーチのまま拡張しても野暮ったいだけで意味ないなー、と思うので気が乗らない。でも、大量のデータを扱うわけじゃなければ、この程度の手抜き実装でも普通に何とかなるものですよねー、とは思わなくもない。無理に洗練されたものを作ろうとせず、領域を見極めて労力を最小限にしたいと私は思ふ。いや、手抜きじゃなくて。手抜きですけど。

今後のlinq.xml.jsは微妙に機能足りてないというか、クロスブラウザ回りがグダグダなのと(Attributeにstyle指定がIEだとコケるとか!) DOMと混ぜ合わせてると気になるところが幾らかあるので、ボチボチと作っていきます。素のlinq.jsには深さ優先探索/幅優先探索を入れようとしているのですが、PC入れ替え中なのでもうしばらく後。

一体誰にアピールしてるの?な寂しさを感じつつも、それを言ったら前回の記事なんてどこからどうみても露骨なまでに受け狙いに走ったわりには大不発で寒すぎたので、それを考えたらマシです、いや、よくわからないけれど。でも簡単アピールは何か違う気がする……。うーん。まあ、誰が使わなくても自分は使うわけなので適当に行きましょふ。

最もタメになる「初心者用言語」はVisualStudio(言語?)

本題、に入る前にlinq.jsの更新内容を。今回からVisualStudio上でJavaScriptを書く際にIntelliSense(コード補完)のサポートが効くようになりました。これでもうリファレンスと睨めっこしなくてもすみます。IntelliSenseが出てくれば使えて、出てこなければ使えない。引数の型までは補完してくれませんが、メソッドチェーンに関しては100%信頼出来ます。利用するにはVisualStudio2008 SP1と対応パッチをあてる必要があります。私はProfessionalで確認していますが、Express Editionでも行けると思います。

もう一つ、linq.xml.jsのほうは、今回からXML/DOMの関数型構築が可能になりました(関数型構築の詳細は前回記事を参照ください)。それに伴い実例として、同梱してあるTwitterビューアサンプルも関数型構築に、ついでに@usernameにアンカーを張るように変更しました。

DOM構築時にLinqクエリを埋め込めると何が便利なの?のサンプル。全体像(obj.textって何だよ、とか)は同梱のものを見てください。obj.textは「@hoge hugahuga @foo hogehoge」な感じ(ようはTwitterの投稿の本文)で、文字列置換なら@usernameをa hrefで囲むだけだけど、DOMでやるにはどうしよう→全部マッチさせちゃえばいいぢゃない、それならSelectで射影すると楽ですね、という流れ。E.Matchesはキャプチャ付きの正規表現グローバルマッチが気楽に使えて非常に便利。

VisualStudioの薦め

以前に最もタメになる「初心者用言語」は、という話題で盛り上がっていたけれど、その時JavaScriptを挙げた人の多くが開発環境を入れる必要なく、テキストエディタがあればそれだけで始められるから、といったものが多かったけど、正直どうかなー、と思っていました。1年以上前のネタに今頃乗っかるのって感じですが今頃乗っかてみます。

初心者は、使えるメソッドが分からないから一々リファレンスを引くのに手間取るし(私は今だって文字列のsubstrとsubstringで悩む、特にJavaScriptはこの手の罠が多いし)、しょうもないタイプミス(大文字小文字とか)に悩まされてプログラムが動かない。演算子やカッコにスペースを空ければいいのかどうかも悩むし、一々手で直すのは手間だけど直さなければ汚いし……。

言語の学習って、特に最初はとにかく書くこと、それが最も覚えやすいと思うのです。リファレンスを引く時間も、手動でスペースを空ける時間も、言語の学習とは全く関係ない余計な時間です。限りなくゼロに近づけた方が良い。リファレンスを引く時間がゼロならば、その分だけ書く作業に時間を回せます。だから、テキストエディタじゃなくIDEを使おう。入力補完は一々リファレンスを引く必要もなく使えるメソッドをリストアップしてくれるし、ヘルプを見る必要もなくメソッドの解説が書かれている。補完があるからタイプミスも起こらないし、「長くてもいいから良い名前を付ける」習慣もつく。インデントだのスペースだのカッコの段落だのは、全てドキュメントフォーマッタ(VSではCtrl+E,D)で機械に任せるのが、完璧で綺麗で、何より手間がかからない。

そんなことより前に、そもそもforで変数がどう移り変わっているのかもよくわからないし。デバッガ、あったほうがいいでしょう、絶対に。あるのとないのとでは学習速度の桁が違ってくる。Firebugがあるって?いや、Firebugは確認用には物凄く便利だけど、それの上で開発とか辛くない?私は無理でした。ついでに言えば、Firebugインストールする手間もVisualStudioをインストールする手間も一緒。

Microsoft Visual StudioはWebインストールを選べば何の手間もいらずダウンロードされ、そのままインストールに入り30分程度で終わる(ダイアログの「はい」を押して待つすだけです、Eclipseと違って何入れればいいか、プラグインがどうのこうの、で悩む必要なくそのものがAll in One)、VSの使い方を知るのに30分。周囲に使える人がいれば、勘所を聞くだけで済むので10分。別に何も最初から全てを知る必要はなく、ダミーのASP.NETプロジェクト作って、F9でブレークポイント設定してF5でデバッグ実行してウォッチウィンドウで変数を監視する。それだけ分かれば十二分です(そこを分かるまでが辛い、というのはあります、分かっている人が横にいれば1分で済む話なのに、こんな初歩的な情報はあまりネットに転がってないから分かりようがない。需要がありそうなら書きますが……)

jQueryにも使える

jQuery大人気! やっぱjQueryが使えないとね!と、で、VisualStudioはjQueryに、jQueryオフィシャルが対応しています。どういうことか、というとjQueryのダウンロードページを見てください。Release NoetsとMinifiedとUncompressedと、Documentation: Visual Studio。そう、この公式に配布されているjquery-vsdoc.jsを使う(jquery.jsと同じディレクトリに置くだけです)と、VS上で丁寧に書かれた解説付きの入力補完が効くようになります。

jQueryの特徴といったらメソッドチェーン。ドット打つとの候補が出てくる出てくる、という感覚はとても楽しい。その楽しさを更に加速させるのがIntelliSense。メソッド名何だっけ、引数は何を取るんだっけ、と悩むこともない。ドット打って選ぶだけ。処理の本質部分だけに集中できる。

そういえばオブジェクトっぽい話が分かるかもしれないJavaScript講座が大人気ですけれど、便乗して言うとjQuery使うなら、まずVisual Studio、があれば、もうあとはドット打つだけなので何も考えないでもいいぐらい。んーと、本題はオブジェクトっぽい話、ですけれど、アレですね、何のかんのでオライリーのJavaScript 第5版を読むのが一番分かりやすかった。ケチッてWeb上のチュートリアルをブクマして読んで、なんてやるよりも、この場合は黙って金出して本読むのが時間の節約にも、得られる知識の濃さ的にもよろしいかと思われます。

amazonついでにLINQ本も張っておきます。LINQ本は以前に「LINQテクノロジ入門」という薄い本が出ているのですが、ほとんどLinq to Sqlのことしか書いていないので、少し高いのですが本で学ぶのならこちらの、プログラミングLINQのほうが良いです。初級-中級的にはLinq in Actionのほうがお薦めなのですが翻訳されてないしされる気配もなさそうですね、残念。そうそう、linq.jsも宜しくお願いします。 C#との高い互換性と、リファレンスにおいてあるLINQ Padはリアルタイムに動作を確認可能、なので学習用途ならかなりイイと思います。パフォーマンスは?実装がヘコい?是非とも突っ込みを!

入力補完の嬉しさ

まんじゅうの例を借りると、prototypeにメソッド突っ込んでオブジェクトほにゃららで何が嬉しいのか、というと、入力補完が効く。使えるメソッドがドット打つだけで出てくるし手入力する必要ない。jQueryのようにヘルプは出てきませんが、補完はちゃんと効くのです。同様に何のメソッドがあるのかさっぱり分からないDOM要素に対しても動くし、紛らわしさ全開な文字列に対しても効きます、便利便利。こうして眺めてると、知らないメソッドがあったりして勉強にもなる。

ところで突然C#の話に移って同様に、ポリモーフィズムで何が嬉しいの→入力補完が効く。ジェネリクスの何が嬉しいの→入力補完が効く。動的言語じゃなくて静型な型付け言語(C#とかJavaとか)の何が嬉しいの?→入力補完が超完璧に効く。型推論の何が嬉しいの?→入力補完が超完璧に効く上に更に型を入力する手間も最小限に抑えられる。

// これをそれぞれ二乗する
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// C# 2.0ではこんなクソみたいなコードが
int[] c2 = Array.ConvertAll<int, int>(array, delegate(int i) { return i * i; });
// C# 3.0ではこんなに短く! Rubyっぽく見えませんか?
var c3 = array.Select(i => i * i);

C#2.0だと、本質的な記述部分(i x i)以外の量の方が死ぬほど多くなってしまい、これじゃ嫌われて然りであり動的言語に行きたくもなる。何個intを書かせるんだよボケ、intなんて自明なんだよクソがって感じです。けれど、C#3.0からの型推論+ラムダ式があればとてもすっきりした上に、ちゃんと補完が効く(i x iを記述している最中もiがintであることを示してくれる)ので物凄く書きやすい。JavaScriptもいいけどC#も良いです。無名関数を愛するJavaScripterならC#のラムダ式はもっと愛せると思います(ウザいカッコとreturnが必要ないので)

C#は特にWindows用のデスクトップアプリケーションを作るなら、作りやすさで言えばベスト。.NET Frameworkは嫌だ?やっぱC++でしょって?うーん、ゲームを作るんでもないならC++の性能は必要ないし、作りやすさでいったらC#>超えられない壁>C++ですよ。C#は開発者に優しいゆるふわ言語。C++はF1マシン、とっても扱いにくいけど凄く速い。C#はプリウス。実用性と最先端ハイテクのバランスが絶妙。そもそも公道をF1カーで走ることが無茶苦茶なのです、公道はプリウスで十分どころか、むしろベストというもの。何にでも、適した領域というものがある。

しかも今C#を覚えると、もれなくSilverlightも付いてきます(Flashの開発言語がActionScriptであるように、Silverlightの主な開発言語はC#)。Flashに勝てるわけないだろヴァーカって?何にせよとりあえずツバだけでもつけておくと生きやすいかなーって思いません?Flash業界なんてレッドオーシャンもいいとこ、今から飛び込むならSilverlight。Microsoftは本気なのであっさりと消滅することはないでしょう。本気の時のMicrosoftはとってもしぶとくしつこい。

でもまあ、そんな云々はどうでもよくて、普通にLINQ楽しいですよ!が一番に言いたいことです。ええ、LINQ楽しい。LINQがあるからC#万歳だし、LINQがなくても作ったのでJavaScriptも万歳。linq.jsは初回リリース時にチュートリアルめいたものが書いてあるので、よければどうぞ、というかお願いしますお願いします。

DOMの関数型構築

Linq to XMLのJavaScript移植、もポチポチと進めています。

var xml = new XElement("customer", new XAttribute("id", "c001"),
              new XElement("firstName", "Paolo"),
              new XElement("lastName", "Pialorsi"),
              new XElement("addresses",
                  new XElement("address", new XAttribute("type", "email"), "paolo@devleap.it"),
                  new XElement("address", new XAttribute("type", "url"), "http://www.devleap.it/"),
                  new XElement("address", new XAttribute("type", "home"), "Brescia - Italy")
              )
          );
 
alert(xml.Source.outerHTML); // .Sourceは気にしないでください(最終的には隠蔽されるので)

これは何と、C#にコピペしても動く。つまり互換性100%(笑) 所謂マークアップビルダになります。私がC#っ子だから、というのもあるけれど、JSON風に書くより好き。JSON風だとそれぞれのライブラリが属性対応のため変則的な書き方ルールが用いられていて、それを覚えなきゃいけないけれど、これなら要素に属性と値を突っ込むだけなので学習コストがほとんどない。と、思う。IntelliSenseのサポートも効くし(現在linq.jsをIntelliSenseが効くよう整備中です、土日に書きすすめて公開、したいなあ)。そのかわりnewだらけでちょっと冗長に過ぎるところはあるかもしれない。

// ユーザー名と発言と投稿を持ったクラス
var Twit = function(user, text)
{
    this.user = user;
    this.text = text;
}
 
// とりあえずサンプルデータ
var data1 = new Twit("ika", "いかはいかが");
var data2 = new Twit("tako", "たこやき");
var data3 = new Twit("sakana", "丸焼き");
 
// XmlHttpほげほげだのJSONだのな結果の例として配列
var list = [data1, data2, data3];
 
// リストをSelectでXElementに射影すると自動的に展開される
var xml = new XElement("twitter",
              new XElement("account", "neuecc"),
              new XElement("twits", E.From(list).Select(function(t)
              {
                  return new XElement("twit", new XAttribute("user", t.user), t.text)
              }))
          );
 
alert(xml.Source.outerHTML); // .Sourceは気にしないでください(最終的には隠蔽されるので)

例をもう一つ。例が適当かつ分かり辛くてすみませんなのですが、Selectで射影しておくと、展開されます。何らかのオブジェクトを繰り返してDOMにする、というシーンは多いと思うのですが、forはもう必要ありません。むしろfor禁止。ループは悪。「この配列を」「この形に展開する」と宣言的に記述するだけで良いのです。

var table = new XElement("table",
                new XElement("tr",
                    new XElement("td", "column1"),
                    new XElement("td", "column2"),
                    new XElement("td", "column3")
                ),
                E.Repeat("tr", 5).Select(function(tagName)
                {
                    return new XElement(tagName,
                        E.Range(1, 3).Select(function(i)
                        {
                            return new XElement("td",
                                new XAttribute("style", (i % 2 == 0) ? "background:red" : "background:green"),
                                "row" + i);
                        })
                    )
                })
            );
 
// FirefoxではouterHTML動きませんけど、最終的には隠蔽されるので(ry
document.body.innerHTML = table.Source.outerHTML;

簡単なTableの生成例。これrowになってませんね、あはは。まあ、いいや。関数の引数の中に式を書くという感覚が最初はキモかったのですが、今では平然と書くようになってしまいました。何というLinq中毒……。しかし、JavaScriptだとfunction(){return }のせいでゴチャゴチャして分かり難いものになってしまう。ラムダ式欲しいなあ。{とかreturnとかいらない。Firefox3では省略出来るけど、Chromeはまだ出来ないみたい……。

Firefox3は使えば使うほど嫌いになるという素敵ブラウザなので、本気でChromeに乗り換える気はありますよ!アドオンがなければ自分で作ればいいじゃない、と思うことにしますので、作りたいですね。一応Hello,Worldは書いた。

<script type="text/javascript" src="linq.js"></script>
<script type="text/javascript" src="linq.xml.js"></script>
<script type="text/javascript">
    // Initialize
    X.Initialize(function()
    {
        X.ID("panel").AttachEvent("click", panel_onclick);
    });
 
    // EventHandler
    function panel_onclick(sender, event)
    {
        X.ID("titleLabel").SetValue(event.screenX);
    }
</script>
 
<div id="panel" class="toolstrip-button">
    <span id="titleLabel">Hello, World!</span>
</div>

ええ、ただのHTMLです。ステータスバーのところにHello,Worldが出てクリックできるという、それだけのもの。Chromeの拡張はまだ何が出来るか分からないというか何も出来ないというか、それグリモンでいいよね、的なことしか出来ない感じなので、その間、今のうちにlinq.xml.jsを完成させよう。何かもう意地でも自分は使う、な方向に傾いてる気がしますが。jQuery?何それ。

JavaScriptで総当たり

Baker, Cooper, Fletcher, MillerとSmithは五階建てアパートの異なる階に住んでいる。Bakerは最上階に住むのではない。Cooperは最下階に住むのではない。Fletcherは最上階にも最下階にも住むのではない。MillerはCooperより上の階に住んでいる。SmithはFletcherの隣の階に住むのではない。FletcherはCooperの隣の階に住むのではない。それぞれはどの階に住んでいるか。

// 条件を並べて総当たり問題を解く
var apart = E.Range(1, 5);
var answers = apart
    .SelectMany(function(baker){ return apart
    .SelectMany(function(cooper){ return apart
    .SelectMany(function(fletcher){ return apart
    .SelectMany(function(miller){ return apart
    .Select(function(smith){ return {
        baker: baker, cooper: cooper, fletcher: fletcher, miller: miller, smith: smith}})})})})})
    .Where("E.From($).Distinct('$.Value').Count() == 5")
    .Where("$.baker != 5")
    .Where("$.cooper != 1")
    .Where("$.fletcher != 1 && $.fletcher != 5")
    .Where("$.miller > $.cooper")
    .Where("Math.abs($.smith - $.fletcher) != 1")
    .Where("Math.abs($.fletcher - $.cooper) != 1");
 
 // 動作確認、Templateのお陰で記述がとても楽
answers.ForEach(function(obj)
{
    var str = Linq.Tools.Template("baker : {baker},\
       cooper :{cooper},\
       fletcher :{fletcher},\
       miller : {miller},\
       smith : {smith}",
       obj);
    alert(str); // とりあえずalertで確認
});

メソッド構文の最大の敵、多重fromを何とかする。今回の場合はカッコを閉じず、最後にSelectを使えばクエリ構文に近い見た目が確保できる。大量の閉じ括弧と、大量のWhereに対してラムダ式が面倒くさくなるのだけはどうにもならないのだけれど。C#で一通り書いてから、JavaScript + linq.jsに書き直したのが上記のもの。C#のは載せませんけれど、ほとんど一緒です。

Distinctの部分だけはlinq.jsのほうがC#より書きやすい。匿名型を突っ込んでもハッシュなので、そのままセレクター渡して比較出来ちゃうので。あとWhereラッシュに対して$がGroovyのitのようなデフォルトのイテレータ引数として機能するので記述が楽なのは利点といえば利点。itのようなものはC#でも欲しいなあ、と思ってたりはする。

パフォーマンス?気にしたら負け、かな……。Chromeなら十分な速度で動くし。理想ではJavaScriptな人にもC#やLINQの良さが伝えられればな!なわけなのですが、現実は非常に厳しく誰もDLしてないよねですよね的ではあるけれど。うーん。

まあ、とりあえず、JavaScript的に特異なコードが書けるのでお遊びにでも使ってもらえると嬉しいです。

linq.js ver 1.3.0.0 - Unfold, Matches, etc…

今回は本体も含めて大量更新です。生成子にUnfoldとMatchesを追加しました。Unfoldは応用範囲がとても広く(広すぎて逆に使い道が思いつかない)、Matchesは正規表現がより使いやすくなるので、実用度が非常に高いと思われます。そして、FromにStringを入れたときの動作を一文字毎に分解するよう変更。更に操作用メソッドにも、Insert, IndexOf, LastIndexOfを追加しました。あと、その他色々。

Unfold

UnfoldはAggregateの反対、といっても良くわからないのですが、引数と戻り値の型が同一の関数を無限に適用し続ける、という感じです。例えばE.Unfold(0, “$+3″)は、初期値が0で、適用する関数が+3。というわけで、0,3,6,9….と無限に3ずつ増幅していく値が取り出せます。これだけでは終了しないので、終了条件はTakeかTakeWhileで別に与えます。

// フィボナッチ数列の無限リスト
var fib = E.Unfold({ a: 1, b: 1 }, "{a:$.b, b:$.a + $.b}").Select("$.a");
// 10個分だけ画面に出力 - 1,1,2,3,5,8,13,21,34,55
fib.Take(10).WriteLine();
 
// abcdefを一文字ずつ削っていく- abcdef,bcdef,cdef,def,ef,f
E.Unfold("abcdef", "$.substr(1)").TakeWhile("$.length>0").WriteLine();

例はフィボナッチ数列で、これは熟練した C# 使いは再帰を書かない? - NyaRuRuの日記の丸コピです。タプルの片方を計算用の一時領域として使う、という感じでしょうか?

static void Main(string[] args)
{
    // 16進の文字列をByte配列に変換する
    var str16 = "FF-04-F2 B3 05 16F3";
 
    var regex = new Regex(@"[abcdef\d]{2}", RegexOptions.IgnoreCase);
 
    // 全部マッチさせてから変換
    var byteArray1 = regex.Matches(str16)
        .Cast<Match>()
        .Select(m => Convert.ToByte(m.Value, 16));
 
    // Unfold使って一つずつ変換
    var byteArray2 = Unfold(regex.Match(str16), m => m.NextMatch())
        .TakeWhile(m => m.Success)
        .Select(m => Convert.ToByte(m.Value, 16));
}
 
static IEnumerable<T> Unfold<T>(T seed, Func<T, T> func)
{
    while (true)
    {
        yield return seed;
        seed = func(seed);
    }
}

UnfoldをC#で正規表現のマッチに使ってみた。あまり意味はない。これをJavaScriptでやると、マッチオブジェクトにNextMatch()がないので、lastIndexの変化したRegExpにexec()し続ける必要がある。というわけで、Timesを使えば同じことができます。E.Times(”regex.exec(input)”).TakeWhile(”$ != null”) です。String.match(オプションはglobal)を使って配列を取得したほうが楽なのですが、それだと文字列配列(みたいな何か)であって、個々のマッチオブジェクトが取れないので、個々のindex(一致した位置)やキャプチャが必要な場合はRegExp.execを使う、という使い分けかなー、と私は思っています。

Matches

そんなことを考えていたら、やっぱRegExp.execのglobalって使いづらいね、と思ったのでE.Matchesを追加しました。C#のRegex.Matchesと同じようにマッチオブジェクト全てを返します。配列で欲しい場合はToArray()を。そのまま処理を加えたい場合は、Linqのメソッド群全てが使えます。マッチのうち先頭だけが欲しいけど射影処理もしたい場合はMatches().Select().First()という手が使えます。

var input = "abcdefgABzDefabgdg";
E.Matches(input, "ab(.)d", "i").ForEach(function(match)
{
    for (var prop in match)
    {
        document.write(prop + " : " + match[prop] + "<br />");
    }
    document.write("toString() : " + match.toString() + "<br />");
    document.write("<br />");
});
 
E.Matches(input, /ab(.)/i); // こうも書ける、gフラグはつけなくていい
E.Matches(input, "ab(.)d"); // 大文字小文字を区別するならflag無しで

E.Matches(input, pattern, flags)で、patternは文字列でも正規表現オブジェクトでも、どちらでも可能。flagsは省略可、与える場合は仕様通り”i”, “m”, “im”が使えます。gフラグを明示的に与える必要はありません。与えても与えなくても関係なくglobalで検索します。

中に入るマッチオブジェクトなのですが、[0]にマッチした文字列全体、キャプチャがある場合は[1]以降にキャプチャした文字列が入ります。あとは.indexと.input。IEだと.lastIndexも取れてますが、IE以外のブラウザではlastIndexは使えません(undefinedでした)

From(String)

今までE.From(”hoge”).ToArray()とすると[0]=”hoge”になっていました。つまりE.Repeat(”hoge”,1)というわけです。これは、何というか、意味ないですよね。C#だと[’h',’o',’g',’e']というように、Charに分解します。というわけで、E.From(”hoge”).ToArray()の結果が[”h”,”o”,”g”,”e”]になるように動作を変更しました。

var input = "こんにちは みなさん おげんき ですか? わたしは げんき です。\
             この ぶんしょう は いぎりすの ケンブリッジ だいがく の けんきゅう の けっか\
             いかりゃく"
 
var result = E.From(input.split(/[\s\t\n]/))
    .Select(function(s)
    {
        return (s.length > 3)
            ? s.charAt(0)
              + E.From(s).Skip(1).Take(s.length - 2).Shuffle().ToString()
              + s.charAt(s.length - 1)
            : s;
    })
    .ToString(" ");
 
alert(result);

サンプルとして、流行から100歩遅れてケブンッリジ変換をlinq.jsで。

真ん中の、「E.From(s).Skip(1).Take(s.length - 2).Shuffle().ToString()」という部分が「こんにちは」を「こんちには」に変換する部分です。E.From(s)で文字列を一文字ずつにバラしているわけです。Skip(1).Take(s.length-2)が、「最初と最後の文字を除く」です。実際の実行例は下のURLからどうぞ。

ケブンリッジ ジェネレータ

↑で動かしているものは、必ずシャッフルされるようにしたり、「、。!?」が末尾の時には別の処理をしていたりと、例に出しているコードとはちょっと違いますけれど。詳しくはソースを見てください。

その他

他に追加したメソッドはぶっちゃけ全然使い道ないのでササッと書き流します。まずInsertですが、これはConcatの場所自由版という感じにシーケンスを特定場所に挿入。IndexOf, LastIndexOfは位置の発見で見つからない場合は-1を返すというお馴染みな動作をします。他にlinq.tools.jsにHashSetを追加しました。これはC#のHashSetに似たようなもので、詳しくはリファレンス見てください。そして、Stopwatchに静的メソッドBenchを追加。これは、どうせStopwatch使うのはベンチマークの時だけでしょ?ということで。

var result = Linq.Tools.Stopwatch.Bench(1000, function()
{
    E.Range(1,100).Where("$%2==0").Select("$*$").Force();
});
document.write(result + 'ms')

第一引数に繰り返し回数、第二引数に実行する関数。わざわざDateの引き算をすることなく、気楽に計れるので、便利といえば便利。というか、この手のものは手間がかかるとついつい避けてしまうので、サクッと計れないとダメですものね。

Stopwatch

昨日の今日で変更というのも申し訳ないのですが、メソッド名を変えました。DateFormatはDateUtility.Format、DateParseはDateUtility.Parseになります。すみません。それだけじゃアレなので、その月の日数を返すDateUtility.DaysInMonthと、うるう年かどうかを判定するDateUtility.IsLeapYearを追加。それと、ストップウォッチクラス。

// Stopwatchを生成・開始して……
var sw = Stopwatch.StartNew();
// 重たい処理をしたりして
E.Range(1, 10000).Select("$*$").Force();
// Elapsed()でms単位で表示
alert(sw.Elapsed() + "ms");
 
/* その他のメソッド */
var sw = Stopwatch.Create(); // Stopwatchの生成(開始はしない)
sw.Start(); // 開始/再開
sw.Stop();  // タイマー停止
sw.Reset(); // タイマー停止+経過時間リセット
sw.IsRunning(); // 動いてるか止まってるか

そろそろ変数名とかインデントは、郷に入り手は郷に従うべきですよねー。ていうか、単純にJavaScriptでC#的な書き方を持ちだすと普通に宜しくない。特に、コンストラクタとメソッドを区別するために大文字小文字にする必要性はとても感じる。と言いつつも、懲りづに続けていたりいなかったり。

それにしても本体を更新していないのにバージョン番号が増殖していくのはどうかと思う。しかも今回のStopwatchとDateTime系のはlinq全く関係ないという有様。

linq.tools.js

更新しました。今回は本体じゃなくて、ユーティリティスクリプトの追加が更新内容になります。少し便利なあると嬉しいちょっとした関数群をlinq.jsを使って。つまりそれってようするにただのサンプル……とは言ってはいけません。実際問題、sampleフォルダに入れちゃってますけどね!

中身は前回のテンプレート置換、配列を使って文字列を追加するStringBuilder、HTMLタグのエスケープをするHtmlEncode/Decode、クエリストリングをオブジェクトに変換するParseQueryStringとオブジェクトをクエリストリングに変換するToQueryString、そして書式を利用してDateを文字列に変換するDateFormatと文字列からDateに変換するDateParse。

詳しくは同梱のリファレンスを参照してください。それだけでもアレなので中身を幾つか。

Linq.Tools.HttpUtility.ParseQueryString = function(query)
{
    return E.From(query.split('?'))
        .SelectMany("$.split('&')", "a,b=>b.split('=')")
        .Where("$.length == 2")
        .ToObject("decodeURIComponent($[0])", "decodeURIComponent($[1])");
}

と、こんな感じでLinqを活用しています。?でバラして&でバラして=でバラしてオブジェクトに変換、と、QueryStringを見てまんま、思考のまんまに記述出来ます。便利便利。こういうのは個人的にもうforで書きたくないのですが、どうでしょう。

DateFormat / DateParse

Linq.Tools.DateFormat(date, format)はその名の通り、書式に基づいて日付を文字列に変換します。このぐらい標準で用意してあるといいのになあ……。formatは大体C#のに従う感じで、それなりに豊富。曜日も出せます。

Linq.Tools.DateFormat = function(date, format)
{
	// 変数準備部省略
 
    var PadZero = function(str, width)
    {
        var count = width - str.length;
        return E.Repeat("0", count).ToString() + str;
    };
 
    var formatDict =
    {
        yyyy: year,
        yy: year.substring(2),
        y: year.substring(3),
        MM: PadZero(month, 2),
        M: month,
        dd: PadZero(day, 2),
        d: day,
		// 以下略
    };
 
    var regex = new RegExp(E.From(formatDict).Select("$.Key").ToString("|"), "g");
    return format.replace(regex, function(m) { return formatDict[m] });
}

変数部分がダラダラ長いので省略してます。0埋め関数が地味にLINQ。で、中身はいつものマンネリな手口です。置換用辞書作って、キー抜き出して正規表現作って、置換する。というだけです。実質二行。辞書の持ち方が若干富豪ですが、この程度なら全然気にならないでしょふ。あ、正規表現はこれだと順序が大事なので(yyyyの前にyyがマッチングされては困る)オブジェクトのキーを並べるやり方だと少し不安だけど、まあ、大丈夫でしょう!辞書の中身が動かないのだから、別に動的生成する必要は全くないどころか百害あって一理無しなのですが、それもまあ、無視黙殺。

var day = Linq.Tools.DateParse("2009-11-12 131034", "yyyy-MM-dd hhmmss");
var str = Linq.Tools.DateFormat(day, "yyyy/MM/dd HH:mm:ss");

FormatがあったらParseもセットだよね、ということでParseも作りました。更に手抜きで、桁数が揃っていないと変換出来ません。だから使えるのはyyyy|MM|dd|HH|mm|ssだけです。hhはダメです。紛らわしくてすみません。あと、動作はyyyyとかのフォーマット文字しか見てないので、それ以外は何だっていいです。_でも空白でも、文字数だけ合わせれば動きます。とても適当。ソースも実にスッカスカ。

Prev | | Next

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual C#

April 2011
|
March 2013

Twitter:@neuecc