Вы определяете это так:
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
, вот как на самом деле работает letrec
.См. 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
до 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) ...)
здесь возвращается из этого фрейма среды, в котором 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)
превращается в приложение (h h)
, поскольку g
указывает на h
, определенное в кадре среды над средой вкоторый объект замыкания был создан.То есть там, в верхней let
форме.Но мы уже видели сокращение вызова (h h)
, который создал замыкание, то есть функцию одного аргумента n
, служащую нашей factorial
функцией, которая на следующей итерации будет вызываться с 4
, затем3
и т. Д.
Будет ли это новый объект замыкания или этот же объект замыкания будет использоваться повторно, зависит от компилятора. Это может повлиять на производительность, но не на семантикурекурсии.