いろいろ試行錯誤をしていて
JavaScript の関数を作って呼び出すのと Haskell のそれとの決定的な違いを見つけた。
それは、たぶんものすっごい単純で当たり前なことだけど、これが分かったとたんに僕の周りのピースが一気に繋がったので、恥をしのんで書く。
僕は以下のように脳内変換していて
a = 1 iszero i = if (i == 0) then True else False
a = function() { return 1 } iszero = function(i) { return i()/*評価は関数呼出し*/ == 0 ? True : False } // True False はこう True = function() { return true } False = function() { return false }
実は、これは全然間違っていて
その間違いが JavaScript の関数と Haskell の関数の違いだということが分かった
例えば、以下で
a = 1 b = a c = if b = 1 then True else False
c を呼び出すと当然 True になる。
しかし、これを JavaScript でやると
a = function() { return 1 } b = function() { return a } c = function() { if (b()/*評価したつもり*/ = 1) then True else False }
で、 Haskell の遅延評価をするとは JS 的にはどう表現すればいいか
/** すべての JS のオブジェクトで e という「値の評価」メソッドを使えるようにする。 **/ // 関数じゃない場合はそのまま返す // (this.valueOf() の返す値はキホンは this と同じ) // (valueOf を使ったのは、プリミティブ値のオブジェクト化対策) Object.prototype.e = function() { return this.valueOf() }; // 引数が 0 個じゃない関数の場合はそのまま返す // 引数が 0 個の関数の場合は呼び出して、更にそれを評価する。 Function.prototype.e = function() { return this.length != 0 ? this : this().e() }; a = function() { return 1 } b = function() { return a } c = function() { return b.e()/*評価!*/ == 1 ? True : False } // True False はこう True = function() { return true } False = function() { return false }
c を評価してみる。
alert(c.e()); // => true
キタコレ
そして、もう一個見つけたこと
Haskell の遅延評価は、「必要なときにだけ行われる」という風に言われているが、必要なときというのは「パターンマッチング」と「if 式などの分岐」のときだけだと分かった。
流れが変わるときに具体的な値が必要になる。うんうんうん!そうだ。分かってきた!
さらに、もう一個見つけたこと
Haskell の関数適用
a b c
は
JavaScript の
a (b) (c);
ではなく
a (function() { return b }) (function(c) { return c });
だ
さらに、もう一個見つけたこと
引数を付けて呼び出した時は、その上に被っている「何にもしない引数なしの関数」は無視されなければならない><
JavaScript の関数適用では乗り越えられない><
var a = function() { return function(x) { return x; } }; a(1) // Haskell での関数だと、ここで 1 が直接 x に渡るような感じ
これをやる方法は JavaScript の関数適用をまったく使わないという感じになるんだろう。
このエントリーではめんどいのでやらない。
(引数の型が関数の場合に引数を function() { return ... } で囲まないことにして逃げる><)
もう一回このエントリーの Haskell のリストを全部 JavaScript にしてやるぜ!
あのときのオラとは違うぜ
これを
a = [] b = [1] c = [1,2] d = [1..10] e = [1..] f = [1,3..] g = [ x * x | x <- f ]
つまりこれを
a = [] b = (:) 1 [] c = (:) 1 ((:) 2 []) d = ff 10 where ff 0 = [] ff i = (:) 1 (map (+1) (ff ((-) i 1))) e = (:) 1 (map (+1) e) f = (:) 1 (map ((+) ((-) 3 1) f) g = map (\x->x*x) f
JavaScript に直すお><
// 最初のほうに書いたやつ Object.prototype.e = function() { return this.valueOf() }; Function.prototype.e = function() { return this.length != 0 ? this : this().e() }; // log 用 log = function(e) { console.log(e.e()); // *評価* して表示 } /** 型を作る **/ // data [] a = [] | (:) a ([] a) EndOfList = function() { return [] }; List = function(x) { return function(xs) { return [x, xs] } }; /** 演算子を定義 **/ // (==) :: Integer -> Integer -> Boolean eq = function(a) { return function(b) { return a.e() == b.e() } }; // (-) :: Integer -> Integer -> Integer sub = function(a) { return function(b) { return a.e() - b.e() } }; // (+) :: Integer -> Integer -> Integer add = function(a) { return function(b) { return a.e() + b.e() } }; // (*) :: Integer -> Integer -> Integer mul = function(a) { return function(b) { return a.e() * b.e() } }; /** リスト操作関数を定義 **/ // (!!) :: [] a -> Integer -> a // (!!) ((:) x xs) i = if i == 0 then x else (!!) xs ((-) i 1) get = function(xs) { return function(i) { /* ここからパターンマッチ */ xs = xs.e(); // パターンマッチ時に *評価* される(head と tail の分割する部分の評価だけ。リストの中身の値の評価はない) if (xs.length == 0) throw Error('index too large'); var x = xs[0]; xs = xs[1]; /* ここまでパターンマッチ */ return eq(function() { return i })(function() { return 0 }).e() ? x : get(function() { return xs })(function() { return sub(function() { return i })(function() { return 1 }) }); // if の条件部は *評価* される } }; // map :: (a -> b) -> [] a -> [] b // map f [] = [] // map f ((:) x xs) = (:) (f x) (map f xs) map = function(f) { return function(xs) { /* ここからパターンマッチ */ xs = xs.e(); // パターンマッチ時にここだけ評価されるから if (xs.length == 0) return xs; var x = xs[0]; xs = xs[1]; /* ここまでパターンマッチ */ return List(function() { return f(function() { return x }) })(function() { return map(f)(function() { return xs }) }) } }; /** 各種リストを定義 **/ /*** 1 個目 ***/ // a = [] a = function() { return EndOfList }; // a[0] -- Error try { log(get(function() { return a })(function() { return 0 })); } catch(e) { log(e) } /*** 2 個目 ***/ // b = [1] // b = (:) 1 [] b = function() { return List(function() { return 1 })(function() { return EndOfList }) }; // b[0] -- => 1 log(get(function() { return b })(function() { return 0 })) // b[1] -- Error try { log(get(function() { return b })(function() { return 1 })); } catch(e) { log(e) } /*** 3 個目 ***/ // c = [1,2] // c = (:) 1 ((:) 2 []) c = function() { return List(function() { return 1 })(function() { return List(function() { return 2 })(function() { return EndOfList }) }) }; // c[0] -- => 1 log(get(function() { return c })(function() { return 0 })) // c[1] -- => 2 log(get(function() { return c })(function() { return 1 })) // c[2] -- Error try { log(get(function() { return c })(function() { return 2 })) } catch(e) { log(e) } /*** 4 個目 ***/ // d = [1..10] // d = ff 10 // where // ff 0 = [] // ff i = (:) 1 (map (+1) (ff ((-) i 1))) d = function() { var ff = function(i) { return eq(function() { return i })(function() { return 0 }).e() ? // ここで *評価* する EndOfList : List(function() { return 1 })(function() { return map(add(function() { return 1 }))(function() { return ff(function() { return sub(function() { return i })(function() { return 1 }) }) }) }) }; return ff(10) }; // d[0] -- => 1 log(get(function() { return d })(function() { return 0 })) // d[1] -- => 2 log(get(function() { return d })(function() { return 1 })) // d[5] -- => 6 log(get(function() { return d })(function() { return 5 })) // d[9] -- => 10 log(get(function() { return d })(function() { return 9 })) // d[10] -- Error try { log(get(function() { return d })(function() { return 10 })) } catch(e) { log(e) } /*** 5 個目 ***/ // e = [1..] // e = (:) 1 (map (+1) e) e = function() { return List(function() { return 1 })(function() { return map(add(function() { return 1 }))(function() { return e }) }) } // e[0] -- => 1 log(get(e)(0)) // e[99] -- => 100 log(get(e)(99)) /*** 6 個目 ***/ // f = [1,3..] // f = (:) 1 (map ((+) ((-) 3 1) f) f = function() { return List(function() { return 1 })(function() { return map(add(function() { return sub(function() { return 3 })(function() { return 1 }) }))(function() { return f }) }) } // f[0] -- => 1 log(get(f)(0)) // f[1] -- => 3 log(get(f)(1)) // f[10] -- => 21 log(get(f)(10)) /*** 7 個目 ***/ // g = [ x * x | x <- f ] // g = map (\x->x*x) f g = function() { return map(function(x){ return mul(function() { return x })(function() { return x }) })(function() { return f }) } // g[0] -- => 1 log(get(g)(0)) // g[1] -- => 9 log(get(g)(1)) // g[10] -- => 441 log(get(g)(10))
うおおおおお
やっとできたあああああああああ(涙)
無限リストも出来たし、遅延評価もできたー><うれしすぎるーー><><
まとめ
遅延評価について
- 評価とは式が無引数関数の場合に呼出し続けること
- 遅延とは引数がそのまま値を返す無引数関数で囲まれること
- 関数適用が行われたときに外側にある「何にもしない引数なしの関数」は無視される
たぶん、間違ってるけど今の理解はこんな感じ。
未来の自分は理解しているのだろうか