From some of Kent's students, I have learned the following: letrec
is implemented in terms of let
using macro expansion. It expands the letrec
into a let
that uses set!
inside. So your first example would expand to this:
(let
([sum (void)])
(set! sum (lambda (x) (if (zero? x) 0 (+ x (sum (- x 1))))))
(sum 5))
Your second, similarly (note that the nested let
s are a result of the let*
- also, this may not be a completely correct expansion, but is my best guess):
(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))
I am not 600% sure how the named let
expands, but Eli suggests that it would be implemented in terms of letrec
itself (which makes sense and should be pretty obvious). So your named let
moves from a named let
into a letrec
into an unnamed let
. And your rewrite of the second looks almost exactly like the expansion of it anyway.
If you are interpreting it and looking for good performance, I would lean toward the letrec
because it is one shorter macro-expand step. Also, let
gets turned into a lambda so you're using define
s in your second example instead of set!
s (which may be heavier).
Of course, if you're compiling, it will probably all fall out in the compiler anyway so just use whichever you think looks nicer (I'm partial to letrec
because let
loops remind me of imperative programming but ymmv). That said, it should be up to you, stylistically (since they are more or less equivalent).
That said, let me provide you an example that you may find worthwhile:
(letrec
([even? (lambda (n) (if (zero? n) #t (odd? (- n 1))))]
[odd? (lambda (n) (if (zero? n) #f (even? (- n 1))))])
(even? 88))
Using your internal define
style will yield:
(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))
So here the letrec
code is actually shorter. And, honestly, if you're doing to do something like the latter, why not settle for 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))
I suspect that begin
is more of a built-in and, as such, will not get macro-expanded (like let
will). Finally, a similar issue has been raised on the Lisp stack overflow a bit ago with more or less the same point.