次のように定義します。
(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
h
toのアプリケーションは、その上の環境で を指すh
新しい環境フレームを作成します。g
h
(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 つの引数の関数。g
h
n
g
h
U
したがって、このクロージャーが呼び出されると、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)
g
h
let
(h h)
n
factorial
4
3
新しいクロージャ オブジェクトになるか、同じクロージャ オブジェクトを再利用するかは、コンパイラに依存します。これはパフォーマンスに影響を与える可能性がありますが、再帰のセマンティクスには影響しません。