不動点演算子
昨日のYコンビネータを考えてるときに気がついたことをメモ。
準備として、不動点演算子とは、任意の関数f
に対して
Y(f) == f(Y(f))
を満たすYのこと*1。
昨日の最後のコード
y = lambda{|q| p = lambda{|f| lambda{|n| f[f][n] } } g = lambda{|u| lambda{|v| q[u[u]][v] } } p[g] } h = lambda{|slf| lambda{|m| if m == 0 1 else m * slf[m-1] end } } fact = y[h] p fact[10]
で、h
の型を考えてみた。型付けの表現はHaskell風の擬似コードで。
h :: (Int -> Int) -> Int -> Int
カリー化されてるのでh
は、『(整数→整数)な関数を受け取って、(整数→整数)な関数を返す関数』となる。
h
の実装を見ると、
h :: (Int -> Int) -> Int -> Int ^^^^^^^^^^^^ ^^^^^^^^^^ factorial factorial
になっている。もう少し詳しく言うと、「階乗関数を入れると階乗関数を返す」関数になっている。そのほかの関数を入れた場合はその限りではない。
このh
から階乗関数を出してくるということは、
h(fact) = fact
となるfact
を求めることで、つまりh
の不動点を求めることになっている。
それをやるのが、Yコンビネータのy
となる。
これで以前からよくわからなかった
がつながった。
まあ、y
のあんな感じの実装で不動点を生成するというのは、パッと見で全然見当つかないし、ふしぎだなぁという感覚は残っているけど。
あと、Haskellで
y f = f (y f)
と定義したらちゃんと動くんだろうか。展開される様が想像できない。
y f = f (y f) fact' :: (Integer -> Integer) -> Integer -> Integer fact' _ 0 = 1 fact' f n = n * f (n - 1) fact = y fact' main = putStrLn (show (fact 10))
おお!動いた。へー。
*1:たぶん