Zコンビネーター、ふたたび

9月12日木曜日、晴れ

夜の『アンダースタンティング コンピュテーション』の勉強会で、いくたびか目のZコンビネーターを理解しようという試みに、ちょっと頑張ってみる。

* * *

再帰関数を、自分の名前を参照せずに定義するために使えるZコンビネーターというものがある。(もう少し単純なYコンビネーターというものもあるけれど、世の中の言語は大抵正格評価のため使えない)

こちらに書いたとおり定義は単純。

「f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)))」がZコンビネーターだ。
「関数を受け取って、一引数の関数を返す」という高階関数を受け取って再帰呼び出し関数を定義するヘルパーとして機能する。

* * *

C++ の lambda で実装できないか試してみたけれど、これは通らなかった。

Haskell でも書いてみたけれど、ダメ。

z = \f -> let g = \x -> f (\y -> x x y) in g g
<interactive>:1:25: error:
   • Couldn't match expected type ‘(t2 -> t3) -> t4’
                 with actual type ‘p’
       because type variables ‘t2’, ‘t3’, ‘t4’ would escape their scope
     These (rigid, skolem) type variables are bound by
       the inferred type of g :: (t1 -> t2 -> t3) -> t4
       at <interactive>:1:15-39In the expression: f (\ y -> x x y)
     In the expression: \ x -> f (\ y -> x x y)
     In an equation for ‘g’: g = \ x -> f (\ y -> x x y)
   • Relevant bindings include
       x :: t1 -> t2 -> t3 (bound at <interactive>:1:20)
       g :: (t1 -> t2 -> t3) -> t4 (bound at <interactive>:1:15)
       f :: p (bound at <interactive>:1:6)
       z :: p -> t (bound at <interactive>:1:1)

<interactive>:1:36: error:
   • Occurs check: cannot construct the infinite type:
       t1 ~ t1 -> t2 -> t3
   • In the first argument of ‘x’, namely ‘x’
     In the expression: x x y
     In the first argument of ‘f’, namely ‘(\ y -> x x y)’
   • Relevant bindings include
       y :: t2 (bound at <interactive>:1:29)
       x :: t1 -> t2 -> t3 (bound at <interactive>:1:20)
       g :: (t1 -> t2 -> t3) -> t4 (bound at <interactive>:1:15)

* * *

手作業で考えてみる。
Zの定義は以下のとおり。

Z = f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)))

ここで関数fにZを適用(つまり代入して書き換え)すると次のようになる。

Z(f)
= (x => f(y => x(x)(y))) (x => f(y => x(x)(y)))

これは左の (x => f(y => x(x)(y))) がxという引数を取る関数で、このxとして右の (x => f(y => x(x)(y))) を渡している、ということ。だからこうなる。(左に出てくるxを (x => f(y => x(x)(y))) で置き換える)

= f(y => (x => f(y => x(x)(y))) (x => f(y => x(x)(y))) (y))

さて、ここで関数fに渡している引数を注意深くみると……

= f(y => Z(f)(y))

ここでZを適用する関数fをこんなものだと考えてみる。

f = zf => x => x == 0 ? 1 : x * zf(x-1)

「Z(f) = f(y => Z(f)(y))」ということを先に確かめている。ここで Z(f) にあらためて factorial という名前をつけてみると、

factorial = f(y => factorial(y))

だとわかる。 f の定義に戻って、第一引数 zf が受け取る関数は「y => factorial(y)」。

factorial
= f(y => factorial(y))
= x => x == 0 ? 1 : x * (y => factorial(y))(x-1)
= x => x == 0 ? 1 : x * factorial(x-1)

この定義は factorial が引数 x を受け取って値「x == 0 ? 1 : x * factorial(x-1)」を返す、と言っている。馴染みのスタイルで書くなら、

function factorial(x) { return x == 0 ? 1 : x * factorial(x-1); }

ということだ。ごく当たり前の再帰関数定義になった。

この記事が気に入ったらサポートをしてみませんか?