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()を呼び出して再び表舞台へ

ライブラリ作ってみた

Mflowthe 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