次のように定義します。
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
これがletrec実際に機能する方法です。Christian Queinnec によるLiSPを参照してください。
あなたが尋ねている例では、自己適用コンビネーターは「Uコンビネーター」と呼ばれ、
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
((U h) 5))
ここでの微妙な点は、letのスコープ規則により、ラムダ式は定義されている名前を参照できないことです。
が呼び出されると、フォームによって作成された環境フレーム内のアプリケーションに((U h) 5)縮小されます。((h h) 5)let
htoのアプリケーションは、その上の環境で を指すh新しい環境フレームを作成します。gh
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
( (let ((g h))
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))
5))
ここでの式は、その上を指す(lambda (n) ...)環境フレーム内から、クロージャーオブジェクトとして返されます。つまり、 、、およびのバインディングも記憶する1 つの引数の関数。ghnghU
したがって、このクロージャーが呼び出されると、nが割り当てられ5、ifフォームが入力されます。
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
(let ((g h))
(let ((n 5))
(if (zero? n)
1
(* n ((g g) (sub1 n)))))))
クロージャオブジェクトが作成された環境の上の環境フレームで定義されたを指すため、アプリケーション(g g)はアプリケーションに縮小されます。つまり、そこまで、最高の形で。しかし、呼び出しの削減についてはすでに見てきました。これにより、クロージャ、つまり 1 つの引数の関数が作成され、関数として機能し、次の反復でなどで呼び出されます。(h h)ghlet(h h)nfactorial43
新しいクロージャ オブジェクトになるか、同じクロージャ オブジェクトを再利用するかは、コンパイラに依存します。これはパフォーマンスに影響を与える可能性がありますが、再帰のセマンティクスには影響しません。