A couple weeks ago I was trying to understand why expressions like this were valid:
Why can you define the x
after its usage in f
? I initially thought
that define
desugared into nested let
's or perhaps let*
, like
this:
But neither made sense, since we still have the same problem of x
being bound after f
is bound. It turns out that define
desugars into
letrec
:
This works because letrec
initializes the bindings for each of the
symbols, to something akin to undefined
. After that, all bindings are
initialized to their values at once. This allows bindings to reference
others before those bindings' definitions exist. The value of x
in f
is only looked up once f
is evaluated. We're referencing symbols
statically, but the lookup is dynamic. That means anything any symbols
referencing symbols that come after have to be wrapped in a lambda
; if
you try to look up too early, it won't work.
; Trying to reference a value immediately
([six (+ x 1)]
[x 5])
#t)
Result:
Error: struct:exn:fail:contract:variable
x: undefined;
cannot use before initialization
You can see this fails at the point that we try to look up x
during
the binding of six
. Now using a lambda
(and with let
since it
behaves in the same way and this example only binds one symbol anyways):
; loophole?
([f (λ () (+ x 1))])
f)
Here, x
isn't bound anywhere, but f
can still be bound and returned!
But if we actually try to evaluate f
, we'll run into problems.
; => Error: struct:exn:fail:contract
([f (λ () (+ x 1))])
(f))