Skip to content

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.

emacs-lisp
(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.

emacs-lisp
(let ()
  (define a 1)
  (define b 2)
  (+ a b))

is equivalent to

emacs-lisp
(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:

emacs-lisp
(let ()
  (define a 1)
  (foo)
  (define b 2)
  (+ a b))

is equivalent to

emacs-lisp
(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:

emacs-lisp
(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.