Computation Expressions
in
Javascript
はじめに
F#の勉強会なのにJavaScriptの話でごめんなさいm(_)m
コンピュテーション式を軸に
別言語の観点からF#や関数型言語のスタイル
に関連した話をします
なんらかのディスカッションのネタになれば。。。
コンピュテーション式
コンピュテーション式って何 ?
the computation expression is "doing something behind the scenes" between each expression.
from Computation expressions: Introduction
今までで一番わかりやすい説明
ということは、
"doing something in behind the scenes"
between each expression
を実現する仕組みをプログラム言語が備えていれば、
そのプログラミング言語でコンピュテーション式を
実現できる??
じゃあ
ECMA Script 6の
ジェネレーターが使えるかも
ES6のジェネレーターとは
First-class coroutines, represented as objects encapsulating suspended execution contexts (i.e., function activations). Prior art: Python, Icon, Lua, Scheme, Smalltalk.
-
コルーチン
-
実行を停止できる
ジェネレーターの利用コード
-
yield を呼ぶと停止して呼び出し元に値を返す
- generator.next()で値を渡して再開する
function *generatorFunction() {
var a = yield 1; console.log(a); // x var b = yield 2; console.log(b); // y return 3; } var generator = generatorFunction(); var ret = generator.next(); console.log(ret.value); // 1 ret = generator.next('x'); console.log(ret.value); // 2 ret = generator.next('y'); console.log(ret.value); // 3
yieldとgenerator.next()
もう一度コンピュテーション式の説明
"doing something in behind the scenes"
between each expression
yieldを呼び出すことで表舞台から裏舞台に
処理を移してdoing something!
generator.next()を呼び出して再び表舞台へ
ライブラリ作ってみた
Mflow: the monad-style flow control library for nodejs
語呂の良さでmonadという単語を使ってしまいました。。
厳密にはmonadではないかも。
monad-styleとコンピュテーション式は同義です(独自定義)。
以下のコンピュテーション式に対応。
- async
- maybe
- lazy
- state
注意: node v0.11以上で--harmony-generatorsオプションをつけないと動きません
Maybe in ES6
var mflow = require('mflow');
var maybe = mflow.maybe;
function add(x, y) {
return maybe(function *() {
var a = yield x;
var b = yield y;
return a + b;
});
}
console.log(add(1, 2)()); // 3
console.log(add(1, null)()); // null
Maybe in F#
open FSharpx open FSharpx.Option let add x y = maybe { let! a = x let! b = y return a + b} [<EntryPoint>] let main argv = printfn "%A" (add (Some 1) (Some 2)) // Some(3) printfn "%A" (add (Some 1) None) // <null> 0
Maybe: F#とES6の比較
ES6
function add(x, y) {
return maybe(function *() { var a = yield x; var b = yield y; return a + b; }); }
F#
let add x y = maybe { let! a = x let! b = y return a + b}
型について
コンピュテーション式を作るにあたって、
静的な型がないとつらいと思っていたが
そんなことはなかった。
「コンピュテーション式」と「型の静/動」は無関係。
動的な型であることによって便利なものが作れることも。
たとえば、Lazyコンピュテーション式で
Lazyな型とそれ以外の型を透過的に扱うとか。
yieldから値を受け取った後は舞台裏で何とでもできる。
Lazy
Lazyな型とそれ以外の型を透過的に扱う
var lazy= require('mflow').lazy; var x = 10; var lazyValue1 = lazy(function *() { return x + 10; }); var lazyValue2 = lazy(function *() { return x * 10; }); function add(value1, value2) { return lazy(function *() { return (yield value1) + (yield value2);
}); }; console.log(add(lazyValue1, lazyValue2)); // 120 console.log(add(1, 2)); // 3
関数について
JSで高階関数がサポートされていなかったら
コンピュテーション式の実現は難しいはず
コンピュテーション式の型は関数になることが多いから
以下はFSharpxの各種コンピュテーション式の定義より
type State<'T, 'State> = 'State -> 'T * 'State type Writer<'W, 'T> = unit -> 'T * 'W type Reader<'R,'T> = 'R -> 'T
Mflowには含めなかったが、WriterもReaderもJSで実現できた
State
コンピュテーション式の型が関数
この例でのState型: Array<int> -> [int, Array<int>]
上のシグネチャ、適当な記法です
var state = require('mflow').state;
function pop() { return function (s) { return [s.shift(), s]; }; } var flow = state(function* () { return yield pop(); });
var result = flow([9, 0, 2, 1, 0]);
console.log(result); // [9, [0, 2, 1, 0]]
まとめ
ES6のジェネレーターを使ってJSに
F#のコンピュテーション式相当を取り入れられる
コンピュテーション式はJSでも便利だと思う
思いの外コンピュテーション式に静的な型は必要なかった
コルーチン(しかも実行の停止と再開で値を双方向に渡し合う)を持つ言語はコンピュテーション式を実装しやすいのでは?
ただし、高階関数をサポートしている必要があるはず
F#のコンピュテーション式の実装は比較的理解しにくい
がコルーチンでの実装はわかりやすいと思う
computation expressions in javascript
By nakamura_to
computation expressions in javascript
- 3,104