ここにいくつかの関連する回答があります(私による):
基本的に、Yを と定義するλr.(λh.h h) (λg.r (λx.(g g) x))
と、アプリケーションは次のようにY r
還元されます。
Y r
(λw.(λh.h h) (λg.w (λx.(g g) x))) r
(λh.h h) (λg.r (λx.(g g) x))
h h
;where
h = (λg.r (λx.(g g) x)) <----\
|
(λg.r (λx.(g g) x)) h |
r (λx.(g g) x) <-------------- | ----------\
;where | |
g = h -----/ |
;so that |
(g g) = (h h) = r (λx.(g g) x) ------/
したがってr
、2 つの引数が必要です。1 つ目は呼び出される再帰関数を表し、2 つ目は実際の引数です。
r = λf (λx. ....x.....(f y)...... )
それは次のように(Y r) x
減少します
(r (λx.(g g) x)) x
(r f) x
;where
f = (λx.(g g) x)
f y = (λx.(g g) x) y = (g g) y = (r f) y ; f is "fixed point" of r
この定義f = (λx.(g g) x)
は、 が呼び出されたときf y
に呼び出される(g g) y
ことを意味し、その時点 g
で自己適用され、r
内部から「プル」され、引数付きで呼び出されg
た結果が返されます。つまり、アプリケーションから生じるラムダ式の本体での呼び出しは、新しい引数を使用した同じ本体の呼び出しに変換されます。(r f)
y
(f y)
(r f)
(r f) y
y
重要な実装の詳細は、それが同じ関数本体であるか、そのコピーであるかですが、セマンティクスは同じです。新しい引数値で同じ関数本体に入ることができます。
Y コンビネータの本質は、参照と自己適用による複製です。同じものを同じ名前で 2 回参照します。したがって、引数としてそれ自体を受け取るように手配します。
純粋なラムダ計算のように参照がなく、パラメーターが引数のテキスト コピーを受け取る場合 (つまり、テキストの書き換えによってリダクションが行われる場合)、同じコピーが複製されて渡され、self への引数として渡されるため、これは機能します。必要に応じて、次の反復で。
ただし、共有参照が使用できる場合ははるかに効率的です (同じ名前を使用する場合はすべて同じものを参照します)。自己参照関数の評価作成の環境モデルの下では、次のように単純です。
(let ((fact #f))
(set! fact
(lambda (n) (if (< 2 n) 1
(* n (fact (- n 1))))))
fact)
実際、あなたの答えの定義は、適用順序 Y コンビネーターの定義です。通常の順序では、無限ループを引き起こすことなく eta-reduction を適用して、Ynorm = (λw.(λh.h h) (λg.w (g g)))
標準的に次のように記述されたものを取得できます。
Ynorm = (λf.(λx.f (x x)) (λx.f (x x)))
確かに
Ynorm g
= (λx.g (x x)) (λx.g (x x))
= g ((λx.g (x x)) (λx.g (x x)))