ArrowのloopとYコンビネーター

言語ゲーム - ArrowLoopで、ArrowのloopでYコンビネーターができるというので、ちょっと試してみた。

Arrowのloopは以前、MaDさんの記事でなんとなく、理解をしていたつもりだったんだけど、実際に自分で書いてみると全然わかっていない、Haskell(主に、if式とlet式)理解していないことをあらためて感じた。

僕の中で、Yコンビネーターといえば、階乗計算なので、階乗計算を書いてみる。
まずは、id:propellaさんのcountdownを真似てみる。

fact (a,f) = ( f a , fc)
    where
        fc 1 = 1
        fc x = x * f (x-1)

loop fact 10

fcという名前がなんとなく、嫌なのでfcを無名関数で記述。

fac (x,f) = (f x, (\y -> if y == 1 then 1 else y*(f (y -1))))
loop fac 10

こう書いたところで、実際にloopに対して渡す引数に、計算するときにfacの引数のタプルのxの部分にしか、値を渡さないのに、なぜfacが返すタプルのfstとsndでloopに渡していないfを利用できるのかが分けわからなくなる。

なので、loopの定義を思い出して追っかけてみる。

loop f b = let (c,d) = f (b,d) in c  -- loopの定義
loop fac 2 -- 大きい数だと簡約の段数が多くなるので、2に
let (c , d ) = fac (2 , d ) in c -- 計算の結果として cが欲しいのでcを求めていく
let (c,d) = ( d 2 , (\y -> if y == 1 then 1 else y*(d (y-1)))) in c
-- 上記よりdは(\y-> if y == 1 then 1 else y * (d (y-1))))となるが、ここのdは遅延評価で簡約(?)されないのが重要
-- cを求めるためにd 2を簡約
let (c,d) = ( 2 * (d (2 -1)) ,(\y -> if y == 1 then 1 else y*(d (y-1)))) in c
-- cを求めるためにd (2-1)を簡約。dは上と同じ
let (c,d) = (2 * 1, (\y -> if y == 1 then 1 else y*(d (y-1)))) in c
let (c,d) = (2,(\y -> if y == 1 then 1 else y*(d (y-1)))) in c
-- これでcが求まるので
2

これで、あっているのかなぁ。簡約という言葉の使い方が間違っている気もするしなぁ。

あらためて、遅延評価難しいと思った。d 2を簡約するときに同時にタプルのsnd側のdも置き換えたくなるもんなぁ。

こうなったら、階乗計算を一行で書きたくなったので、すべて無名関数に

fac = loop (\(x,f)->(f x,(\y->if y == 1 then 1 else f (y-1))))

最初は、loopなんて使って再帰書くよりも、ふつうに再帰使った方が早いよと思っていたんだけど、これを書いているうちに、loopはArrowなので、Arrowのストリームの中に、ループが埋め込まれるというのは、それはそれですっきりするような感じがしました。(なれるまでは、大変だけど)