define
s are like a letrec
Now that you understand letrec
, we can explain what define
really does.
Notice that when you define
top-level variables and procedures,
the procedures you create can refer to other variables in the same
top-level environment.
It is as though all of the top-level bindings were created by a single
big letrec
, so that the initial value expressions create procedures
that can "see" each others' name bindings. Expressions that aren't
definitions make up the "body" of this imaginary letrec
.
Recall that a procedure-defining define
is equivalent to
a variable-defining define
with a lambda
expression
to compute its initial value.
The following top-level forms
... (define (foo) (... (bar) ...)) (define (bar) (... (baz) ...)) (define (baz) (... (quux) ...)) ... (foo) ...
are therefore equivalent to
... (define foo (lambda () (... (bar) ...))) (define bar (lambda () (... (baz) ...))) (define baz (lambda () (... (foo) ...))) ... (foo) ...
When we view top-level define
s as being implicitly like parts of a
letrec
, the program takes the equivalent form
(letrec (... (foo (lambda () (... (bar) ...))) (bar (lambda () (... (baz) ...))) (baz (lambda () (... (foo) ...))) ...) ... (foo) ...)
(Actually, things are scoped like this, but the initial value
expressions of define
s and the non-definition expressions
are evaluated in the order they occurred in the source program. For
top-level expressions, you can depend on Scheme executing the
executable parts of definitions in the order written.)
Local define
s work pretty this way, too. A Scheme interpreter
or compiler recognizes any define
s that occur at the
beginning of a body as being parts of an implicit letrec
;
the subsequent expressions in the same body are treated as
the body of the implicit letrec
.
So the following procedure
(define (my-proc) (define (local-proc-1) ...) (define (local-proc-2) ...) (local-proc-1) (local-proc-1))
is equivalent to
(define (my-proc) (letrec ((local-proc-1 (lambda () ...)) (local-proc-2 (lambda () ...))) (local-proc-1) (local-proc-2)))
If we "desugar" the outer define
, too, we get
(define my-proc (lambda () (letrec ((local-proc-1 (lambda () ...)) (local-proc-2 (lambda () ...))) (local-proc-1) (local-proc-2)))