Footnote #28 of SICP says the following:
Embedded definitions must come first in a procedure body. The management is not responsible for the consequences of running programs that intertwine definition and use.
What exactly does this mean? I understand:
- "definitions" to be referring to both procedure creations and value assignments.
- "a procedure body" to be as defined in section 1.1.4. It's the code that comes after the list of parameters in a procedure's definition.
- "Embedded definitions must come first in a procedure body" to mean 'any definitions that are made and used in a procedure's body must come before anything else'.
- "intertwine definition and use" to mean 'calling a procedure or assigned value before it has been defined;'
However, this understanding seems to contradict the answers to this question, which has answers that I can summarise as 'the error that your quote is referring to is about using definitions at the start of a procedure's body that rely on definitions that are also at the start of that body'. This has me triply confused:
- That interpretation clearly contradicts what I've said above, but seems to have strong evidence - compiler rules - behind it.
- SICP seems happy to put definition in a body with other definitions that use them. Just look at the
sqrt
procedure just above the footnote! - At a glance, it looks to me that the linked question's author's real error was treating
num-prod
like a value in their definition ofnum
rather than as a procedure. However, the author clearly got it working, so I'm probably wrong.
So what's really going on? Where is the misunderstanding?
In a given procedure's definition / code,
define
.define
.define
forms must go first, then all the other internal forms. Once a non-define
internal form appears, there can not appear an internaldefine
form after that.define
forms are gathered into one equivalentletrec
, and follow its rules.Namely, we have,
Any
ai
can be used in any of theinitj
expressions, but only inside a lambda expression.(*) Otherwise it would refer to the value ofai
whileaj
is being defined, and that is forbidden, because anyai
names are considered not yet defined while any of theinitj
expressions are being evaluated.(*) Remember that
(define (foo x) ...x...)
is the same as(define foo (lambda (x) ...x...))
. That's why the definitions in thatsqrt
procedure in the book you mention are OK -- they are alllambda
expressions, and any name's use inside a lambda expression will only actually refer to that name's value when the lambda expression's value -- a lambda function -- will be called, not when that lambda expression is being evaluated, producing that lambda function which is its value.The book is a bit vague at first with the precise semantics of its language but in Scheme the above code is equivalent to
as can be seen explained, for instance, here, here or here.
For example,
first evaluates the lambda expression,
(lambda () a)
, and binds the namefoo
to the result, a function; that function will refer to the value ofa
when(foo)
will be called, so it's OK that there's a reference toa
inside that lambda expression -- because when that lambda expression is evaluated, no value ofa
is immediately needed, just the reference to its future value, under the namea
, is present there; i.e. the value thata
will have after all the names in thatletrec
are initialized, and the body ofletrec
is entered. Or in other words, when all the internaldefine
s are completed and the body proper of the proceduremy-proc
is entered.So we see that
foo
is defined, but is not used during the initializations;a
is defined but is not used during the initializations; thus all is well. But if we had e.g.then here
foo
is both defined and used during the initializations of the internal, or "embedded",define
s; this is forbidden in Scheme. This is what the book is warning about: the internal definitions are only allowed to define stuff, but its use should be delayed for later, when we're done with the internaldefine
s and enter the full procedure's body.