6.10.3 Internal definitions

A define form which appears inside the body of a lambda, let, let*, letrec, letrec* or equivalent expression is called an internal definition. An internal definition differs from a top level definition (see Top Level Variable Definitions), because the definition is only visible inside the complete body of the enclosing form. Let us examine the following example.

(let ((frumble "froz"))
  (define banana (lambda () (apple 'peach)))
  (define apple (lambda (x) x))
  (banana))
⇒
peach

Here the enclosing form is a let, so the defines in the let-body are internal definitions. Because the scope of the internal definitions is the complete body of the let-expression, the lambda-expression which gets bound to the variable banana may refer to the variable apple, even though its definition appears lexically after the definition of banana. This is because a sequence of internal definition acts as if it were a letrec* expression.

(let ()
  (define a 1)
  (define b 2)
  (+ a b))

is equivalent to

(let ()
  (letrec* ((a 1) (b 2))
    (+ a b)))

Internal definitions may be mixed with non-definition expressions. If an expression precedes a definition, it is treated as if it were a definition of an unreferenced variable. So this:

(let ()
  (define a 1)
  (foo)
  (define b 2)
  (+ a b))

is equivalent to

(let ()
  (letrec* ((a 1) (_ (begin (foo) #f)) (b 2))
    (+ a b)))

Another noteworthy difference to top level definitions is that within one group of internal definitions all variable names must be distinct. Whereas on the top level a second define for a given variable acts like a set!, for internal definitions, duplicate bound identifiers signals an error.

As a historical note, it used to be that internal bindings were expanded in terms of letrec, not letrec*. This was the situation for the R5RS report and before. However with the R6RS, it was recognized that sequential definition was a more intuitive expansion, as in the following case:

(let ()
  (define a 1)
  (define b (+ a a))
  (+ a b))

Guile decided to follow the R6RS in this regard, and now expands internal definitions using letrec*. Relatedly, it used to be that internal definitions had to precede all expressions in the body; this restriction was relaxed in Guile 3.0.