I feel that understanding this subtlety might help me to understand how scope works in Scheme.
So why does Scheme error out if you try to do something like:
(define (func n)
(define n (+ 1 n))
n)
It only errors out at runtime when calling the function.
The reason I find it strange is because Scheme does allow re-definitions, even inside functions. For example this gives no error and will always return the value 5 as expected:
(define (func n)
(define n 5)
n)
Additionally, Scheme also seems to support self-re-definition in global space. For instance:
(define a 5)
(define a (+ 1 a))
gives no error and results in "a" displaying "6" as expected.
So why does the same thing give a runtime error when it occurs inside a function (which does support re-definition)? In other words: Why does self-re-definition only not work when inside of a function?
global
define
First off, top level programs are handled by a different part of the implementation than in a function and defining an already defined variable is not allowed.
It might happen that it works in a REPL since it's common to redefine stuff, but when running programs this is absolutely forbidden. In R5RS and before what happened is underspecified and didn't care since be specification by violating it it no longer is a Scheme program and implementers are free to do whatever they want. The result is of course that a lot of underspecified stuff gets implementation specific behaviour which are not portable or stable.
Solution:
set!
mutates bindings:define
in a lambda (function, let, ...)A
define
in a lambda is something completely different, handled by completely different parts of the implementation. It is handled by the macro/special formlambda
so that it is rewritten to aletrec*
A
letrec*
orletrec
is used for making functions recursive so the names need to be available at the time the expressions are evaluated. Because of that when youdefine
n
it has already shadowed then
you passed as argument. In addition from R6RS implementers are required to signal an error when a binding is evaluated that is not yet initialized and that is probably what happens. Before R6RS implementers were free to do whatever they wanted:This actually becomes:
Now a compiler might see that it violates the spec at compile time, but many implementations doesn't know before it runs.
Perfectly fine result in R5RS if the implementers have good taste in books. The difference in the version you said works is that this does not violate the rule of evaluating
n
before the body:becomes:
Solutions
Use a non conflicting name
Use
let
. It does not have its own binding when the expression gets evaluated: