От некоторых учеников Кента я узнал следующее: letrec
реализован в терминах let
с использованием макроразложения.Он расширяет letrec
до let
, который использует set!
внутри.Итак, ваш первый пример расширится до следующего:
(let
([sum (void)])
(set! sum (lambda (x) (if (zero? x) 0 (+ x (sum (- x 1))))))
(sum 5))
Ваш второй аналогично (обратите внимание, что вложенные let
s являются результатом let*
- также, это может быть не совсем корректное расширение, но это мое лучшее предположение):
(let
([sum (void)]
(set! sum (lambda (x) (if (zero? x) 0 (+ x (sum (- x 1))))))
(let
[f (void)]
(set! f (lambda () (cons n n-sum)))
(let
[n (void)]
(set! n 15)
(let
[n-sum (void)])
(set! n-sum (sum n))
(f))
Я не уверен на 600%, как расширяется именованный let
, но Илай предполагает, что он будет реализован в терминах самого letrec
(что имеет смысли должно быть довольно очевидно).Таким образом, ваш именованный let
перемещается из именованного let
в letrec
в неназванный let
.И ваша перезапись второй части выглядит почти так же, как и ее расширение.
Если вы интерпретируете ее и ищете хорошую производительность, я бы склонился к letrec
, потому что это один более короткий шаг макро-расширения,Кроме того, let
превращается в лямбду, поэтому вы используете define
s во втором примере вместо set!
s (что может быть тяжелее).
Конечно, если вы компилируетев любом случае, все это может выпасть в компиляторе, так что просто используйте то, что, по вашему мнению, выглядит лучше (я неравнодушен к letrec
, потому что циклы let
напоминают мне об императивном программировании, но ymmv).Тем не менее, это должно быть на ваше усмотрение, стилистически (поскольку они более или менее эквивалентны).
Тем не менее, позвольте мне привести вам пример, который вы можете найти стоящим:
(letrec
([even? (lambda (n) (if (zero? n) #t (odd? (- n 1))))]
[odd? (lambda (n) (if (zero? n) #f (even? (- n 1))))])
(even? 88))
Использование вашего внутреннего стиля define
даст:
(let ()
(define even? (lambda (n) (if (zero? n) #t (odd? (- n 1)))))
(define odd? (lambda (n) (if (zero? n) #f (even? (- n 1)))))
(even? 88))
Так что здесь код letrec
на самом деле короче.И, честно говоря, если вы делаете что-то вроде последнего, почему бы не довольствоваться begin
?
(begin
(define even? (lambda (n) (if (zero? n) #t (odd? (- n 1)))))
(define odd? (lambda (n) (if (zero? n) #f (even? (- n 1)))))
(even? 88))
Я подозреваю, что begin
является скорее встроенным и, как таковой, не получит макро-расширение (как let
will).Наконец, похожая проблема была поднята при переполнении стека Lisp немного назад с более или менее одинаковой точкой.