Haskell の where 節の謎

Haskell の無限リストを勉強していて、標準関数の repeat :: a -> [a] がなんでこの定義で無限リストになるのかが全然理解できないのでメモしておこう。

repeat :: a -> [a]
repeat x = xs where xs = x : xs

僕の感覚では、無限リストは再起呼び出しみたいな形になっていて、要するに以下のように:

myrepeat :: a -> [a] 
myrepeat x = [x] ++ (myrepeat x) 

関数定義内で関数呼び出しがあるものだと思っていた。

myrepeat は期待通りに動作するんだけど、repeat の実装も試してみたら(当たり前だけど)期待通りの動作をする。

標準関数をざっと見た感じ、cycle が repeat と同じパターンで定義されている。おそらくキモなのは where 節で「xs = x : xs」のように定義されるものを用いて定義しているところだろう。

でもよく見て欲しい。

repeat :: a -> [a]
repeat x = xs where xs = x : xs

関数 repeat は「repeat x は xs であり、xs は x と xs からなるリストである」という定義なんだけど、xs はリストだってことは(型推論で)わかるんだけど、どんなリストなのっていう定義がどこにもなくいきなり使えてしまっている。

試しにこんな関数を実行してみた。

foo = x where x = x + 1 
main = print foo 

結果は以下の通り。

test: <<loop>>

Haskell は初期化されていない(値を束縛していない)変数は存在しないので、暗黙の初期値がリストなら空リストで、数値なら 0 なのかも、と思って、以下のようにしてみたけど、これだとコンパイルエラー。

foo = x -- 0 を期待した

ダメ元で where 節で以下をやってみたけど、やっぱりダメでコンパイルエラー。

foo = x where x -- 値がない

以下もダメ。

foo = x where x = x

以下なら OK。

foo = x where x = x + 1

理屈はよくわからないけど、where 節を使用して「定義されるものを使って定義」することで、再帰呼び出しのような挙動になる(再帰呼び出しも定義されるもので定義しているので理論的には同じだ)、というのは覚えた。