defines are like a letrec
Now that you understand letrec, I 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 defines 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 defines 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 defines work pretty this way, too. A Scheme interpreter
or compiler recognizes any defines 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)))