Reputation: 10930
Is Scheme's letrec
only meant for defining procedures, especially recursive ones? I am asking because it appears to be possible to bind non-procedures using letrec
. For example, (letrec ((x 1) (y 2)) (+ x y))
. If Scheme's letrec
is only meant for procedures, then why isn't its syntax constrained to allow procedures only?
Consider this use of letrec
to define two mutually recursive procedures:
(letrec ((is-even? (lambda (n)
(let ((n (abs n)))
(if (= n 0)
#t
(is-odd? (- n 1))))))
(is-odd? (lambda (n)
(let ((n (abs n)))
(if (= n 0)
#f
(is-even? (- n 1)))))))
(is-even? 123))
If we use Common Lisp's LABELS
instead of Scheme's letrec
, these two mutually recursive procedures would be defined like this instead:
(labels ((even-p (n)
(let ((n (abs n)))
(if (= n 0)
t
(odd-p (- n 1)))))
(odd-p (n)
(let ((n (abs n)))
(if (= n 0)
nil
(even-p (- n 1))))))
(even-p 123))
If letrec
is only useful for defining procedures, then why isn't its syntax constrained like that of LABELS
to allow only procedures in its initialization forms?
Upvotes: 0
Views: 110
Reputation: 9282
It is mostly useful for binding procedures, yes: the variables bound by letrec
can't refer to their own bindings until afterwards, so something like
(letrec ((x (list x)))
x)
Does not work: see my other answer.
However first of all it would be a strange restriction, in a Lisp-1 like Scheme, to insist that letrec
can only be used for procedures. It's also not enforcable other than dynamically:
(letrec ((x (if <something>
(λ ... (x ...))
1)))
...)
More importantly, letrec
can be used in places like this:
(letrec ((x (cons 1 (delay x))))
...)
which is fine, because the reference to the binding of x
is delayed. So if you have streams, you can then do this:
;;; For Racket
(require racket/stream)
(letrec ((ones (stream-cons 1 ones)))
(stream-ref ones 100))
Upvotes: 1
Reputation: 10073
It is useful for defining procedures, but it is not only for that.
The difference between let*
and letrec
is defined in the manual:
Like
let
, including left-to-right evaluation of the val-exprs, but the locations for all ids are created first, all ids are bound in all val-exprs as well as the bodys, and each id is initialized immediately after the corresponding val-expr is evaluated.
The order in which id
s are bound is significant here, as let*
would not be useful for defining mutually-recursive procedures: the first procedure would have an unbound id
for the second procedure.
As your example comes directly from the manual I think you should see that CS information is often dense and terse when reading, but it does say very clearly what the difference is with letrec
.
Upvotes: 0