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

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.