Skip to content

Shibuya.js in Kyoto

motemen edited this page Sep 14, 2010 · 1 revision

Arrow.js

Shibuya.js in Kyoto

motemen (motemen@gmail.com)

Arrow

Arrow というのが Haskell 界隈で流行ってるらしい。

関数 (など) を矢印 (arrow) と見做して、矢印を連結することで処理を繋げていく

Arrow 同士を連結したものもまた arrow になる。

Arrow を作る/実行する

値を一つとって一つ返す関数から arrow を作る: Arrow(f)

function Arrow(f) { … this.cpsFunction = function(x, k) { return k(f(x)) }; … } Arrow.prototype.run = function(x) { var result; this.callCPS(x, function(y) { result = y }); return result; }

Arrow.prototype.run(value) は Arrow に値を一つ渡して実行するメソッド

var aAdd1 = Arrow(function(x) { return x + 1 }); aAdd1.run(2); // => 3

this.cpsFunction

CPS: Continuation Pasing Style

普通の (同期な) コード

function(x) { // … return(x); }

CPS なコード

function(x, k) { // … k(x); }

同期なコードも非同期なコードもコールバック形式にすることで同様に扱える。

Arrow(function(x) { return x + 1 }).run(1); // => 2

Arrow の連結 (>>>)

Haskell の演算子をそのままメソッド名にしているため a['>>>'](b) とする必要がある。@(a)[‘>>>’](b)@ とすることでより納得のいく形になります。:)

「値を a に渡し、その結果を b に渡す」arrow を作る演算子。

var aLoadIndex = Arrow.XHR('index.html');
var aAlert = Arrow(alert);
var aGetAlert = (aGetIndex)['>>>'](aAlert);
aGetAlert.run(); // index.html を xhr でロードして結果を alert する

複数値 (***, &&&)

値を複数の arrow で受け取る

***: 配列を分配

((a)['***'](b)['***'](c)).run([1, 2, 3])
// a(1), b(2), c(3) が実行される

&&&: 一つの入力をすべての arrow に渡す

((aLoadJSON1)['&&&'](aLoadJSON2))['>>>'](aAlert).run()
// aLoadJSON1(undefined), aLoadJSON2(undefined)
// => [json1, json2]

非同期な arrow は全部の結果が返ってくるまで待つ

条件分岐 (|||, +++)

入力値によって複数の arrow のうち 1つを選択する

Arrow は値を 1つしか受け取れないため、値をラップするものが必要:

Arrow.Value.In(n)(x) → n 番目の arrow に値 x を渡す

+++: 入力値で条件分岐

((aHoge)['+++'](aFuga)['+++'](aPiyo))
    .run(Arrow.Value.In(0)(x)) // => aHoge.run(x);

|||: 条件分岐して、結果をまたラップしなおす

「a もしくは b, そのあと f もしくは g」という感じ

(((a)['|||'](b))['>>>']((f)['|||'](g)))
    .run(Arrow.Value.In(1)(x)) // => g(b(x))

Arrow.Error

Arrow 内で例外が発生した場合はエラーオブジェクトが返ってくる

Arrow.Error = Arrow.Value.In(1);

マージ (<+>)

複数の arrow から、最初に返ってきた arrow の値を採用

全部返ってくるまで待つ &&& とは違って…

((aLoadJSON1)['<+>'](aLoadJSON2)['<+>'](aLoadJSON3))['>>>'](aAlert).run()
// => json1 か json2 か json3, 一番早い 1つだけ

Arrow の繰り返し

Arrow.Loop(arrow)

または

arrow.loop()

arrow の返した値をまた arrow に渡してまた… という arrow

→ イベント処理などで使える (あとで実例を)

非同期な Arrow の例

Arrow.Event(node, event)

DOM イベントが発生したら次の arrow に進む

h3 Arrow.Delay(msec)

msec ミリ秒後に次の arrow に進む

Arrow.Stop

何があっても次に続かない

(Arrow.Stop)['<+>'](aHoge)aHoge と同じ

実例

GitHub プレゼンテーションぐりもん

仕様

  • (GitHub の wiki ページ上で) alt-p キーでプレゼンモードに入る
  • プレゼンモードでは j, k でページを進む/戻る
  • もちろんそれ以外のキーは無視

プレゼンモードの状態遷移図は…

プレゼンモードの arrow は

var aSlide =
    (Arrow.Event(document, 'keydown'))
        ['>>>']
    (
        (aIsKey('j')['>>>'](aNextPage))
            ['<+>']
        (aIsKey('k')['>>>'](aPrevPage))
            ['<+>']
        (aNOP)
    )
        ['>>>']
    (aShowCurrentPage);

状態遷移図をそのままコードに落としただけ!(状態遷移図における矢印が arrow に対応している)

Arrow.Event(document, 'keydown') により、ドキュメントで keydown イベントが発生すると次の arrow にイベントオブジェクトが渡される

aIsKey(key) というのは、キーイベントオブジェクトのキーが key である時だけ次の arrow に処理を渡す arrow (というのを作っておく), これと <+> を組み合わせると:

  • “j” キーが押されたとき: aIsKey(‘j’)[‘>>>’](aNextPage) が実行される
  • “k” キーが押されたとき: 上の arrow が次に進まないので、aIsKey(‘k’)[‘>>>’](aPrevPage) が実行される
  • その他のキーが押されたとき: 上の arrow たちが次に進まないので、aNOP (何もしない arrow) に処理が移る

これらのうちどれかが実行されたあと、現在のページを表示する aShowCurrentPage が実行される

プレゼンモードに入る前の状態遷移図は…

var aStart =
    (Arrow.Event(document, 'keydown'))
        ['>>>']
    (
        (aIsKey('alt-p')['>>>'](Arrow.Value.In(0)))
            ['<+>']
        (Arrow.Value.In(1))
    )
        ['>>>']
    (
        ((aSetupSlide)['>>>'](aSlide.loop()))
            ['+++']
        (aNOP)
    );

最初の arrow:

  • alt-p が押されたら 0番目のルートを選ぶ
  • そうでなければ 1番目のルートを選ぶ

その結果を受け取る arrow:

  • 0番目のルートでは: さっきの aSlide を繰り返す arrow (もう返ってこない)
  • 1番目のルートでは: 何もしない

これらの arrow を宣言した上で、

aStart.loop().run()

とするとコードが動き始める…!

loop() しているため aNOP の後は最初に戻ってまた keydown をリスンする

いちおう

英語のメソッド名もあるけど見易いかどうかは…

var aSlide =

    (Arrow.Event(document, 'keydown'))
    .next(
        (aIsKey('j').next(aNextPage))
            .or
        (aIsKey('k').next(aPrevPage))
            .or
        (aNOP)
    )
    .next(aShowCurrentPage);

Arrow 便利な気がしてきた

自分でも半信半疑だったけどわりと便利な気がしてきた!

http://github.com/motemen/arrow-js