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?
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))