fixを無限再帰させないもう一つの方法
引き続きfixのことです…
昨日の文章を書いた後、しつこくRecursive Monadic Bindingsの論文を眺めていました…この論文は主にモナド関数を使ったfixの話なのですが、例のいくつかを眺めていてもfixの使い方が
fix :: ((a -> a) -> a -> a) -> a -> a
ではなく
fix :: (a -> a) -> a
のような感じなのです。うなりながらもちょっとコードをいじくりまわしているうちに(a->a)->aなfixでも無限再帰に陥らない方法があることに気がつきました。
僕が気がついた方法は以下のとおり:
test = fix foo where foo x = 1 main = print test => 1
またしても、当たり前なのですが、これもまたfixを(a->a)->a的に使ったら必ず無限再帰だと思い込んでいた頭には新鮮でした。ここでも再帰をするかしないかは(a->a)な関数に任されていたのです。
さて、変数というのは実は値を引き出す以外にもう一つできることがありますね…それは値を代入することです…Haskellは純粋関数型なので普通には宣言したときに値が決定してしまうので代入という考え方はあまりしないですよね…でも、実はこんなことができちゃうようです:
test = fix foo where foo x = let x = 1 in x main = print test => 1
つまり、Haskellの変数は一度宣言したら値が変えられないのではなくて、一度値が決定したらもう変えられないということなんでしょうか?ひょっとしたらこのコードが動く理由はfixによるところもあるのかもしれません。x = foo xというのは実はxが簡約途中の中間形態でx=1とすることでfoo x = 1 であることが判明してx = 1が簡約の結果として決定するような?
そもそもimmutableな変数で値を宣言時に指定しなくて良いのは関数のパラメタぐらいのものですね…と思ってfixの外で同じことをやってみました:
foz x = let x = 3 in x main = print $ foz 4 => 3
ということでfixとは関係なくできることのようです。でも、
foz x = let x = (3 + x) in x main = print $ foz 4
とやるとコンパイルはできても無限再帰になって終了しません…x = (3 + x)がfixの要領で再帰を起こすんですね…
そして、前回(a->a)->aなfixが使えない理由のもう一つとして書いたことが出入り口がないことでした…でもここまで見てわかるように無限再帰さえ阻止できればちゃんと値は帰ってきますね…それじゃぁ、入り口はどうにかならないでしょうか?
そこで気づいたのがこれです:
foo a = fix (\x -> a + 4) main = print $ foo 5 => 9
これで入り口も確保できました。しかしながら僕のここまでの理解では(a->a)->aなfixではここで挙げたサンプルのように再帰をまったく行わないモードか無限再帰で中断するモードのコードのどちらかしかかけませんでした。((a->a) -> a -> a) -> a -> aなfixのときのように再帰を自由自在に操る方法は今のところ見つかってません…
今日はこの辺で…
ではでは。