In tail position, the bound variable doesn't need to exist. Only the variables available when the procedure is created is available at the call time.
(define (some-function)
;; only global variables exist at this point
(let ((c (read-char)))
;; c exists for a brief time
(some-function)))
After the variable some-function
is evaluated inside the procedure, then c
is removed before applying whatever some-function
was evaluated to. There exists only one c
at a time.`
Imagine this slightly altered version:
(define (some-function previous)
(let ((c (read-char)))
(some-function c)))
Here we have the same except some-function
and c
is evaluated before c
and previous
are removed and the apply is started.
It is a requirement that the procedures and variables are handled in this way. It's called tail call optimization.
When a procedure gets created, the variables that is used in them needs to exist for the duration of the procedures existence:
(define (some-function2)
(let ((c (read-char)))
(lambda () c)))
(define test (some-function2))
(define test2 (some-function2))
Now. The variable is moved to be something connected to the procedure and still not a part of the environment when the some-function2
call is finished. The procedures are called closures and the c
in each are closure variables that live on even after the binding procedure is done. If you are used to other languages you can think of these variables that live on to be on the heap. These "free variables" are what made Scheme special when it was created in the 70s and every programming language since is more or less based on this principle.
let
does not lessen the requirement to implement TCO. – Shannon Severance