This is a “lexical closure”, and you are right that num
, a “private variable” is similar to a static variable in C, for example: it is visible only in the code in the form let
(its “lexical scope”), but it is saved throughout the program, and not reinitialized with every function call.
I think the part you are confusing is this: " num
is created by pasting inside next-num
, it's kind of a local variable." This is not true because the let
block is not part of the next-num
function: it is actually an expression that creates and returns a function that is then bound to next-num
. (This is very different, for example, from C, where functions can only be created at compile time and defined at the top level. In Scheme, functions are values, such as integers or lists, that any expression can return).
Here's another way to write (almost) the same thing, which makes it clearer that define
simply associates next-num
with the value of the expression returning the function:
(define next-num #f) ; dummy value (let ((num 0)) (set! next-num (lambda () (set! num (+ num 1)) num)))
It is important to note the difference between
(define (some-var args ...) expression expression ...)
which makes some-var
function that executes all expressions
when called, and
(define some-var expression)
which binds some-var
to the expression
value evaluated then and there. Strictly speaking, the previous version is not needed because it is equivalent
(define some-var (lambda (args ...) expression expression ...))
Your code is almost the same as this, with the addition of a variable with the lexical scope num
, around the lambda
form.
Finally, here is the key difference between private variables and static variables, which makes locks more powerful. If you wrote the following:
(define make-next-num (lambda (num) (lambda () (set! num (+ num 1)) num)))
then each call to make-next-num
will create an anonymous function with a new variable num
, which is private to this function:
(define f (make-next-num 7)) (define g (make-next-num 2)) (f) ; => 8 (g) ; => 3 (f) ; => 9
This is a really cool and powerful trick that explains most of the languages with lexical closures.
Edited to add: you ask how Scheme “knows” which num
to change when next-num
called. In general terms, if not in implementation, it is actually quite simple. Each expression in the Schema is evaluated in the context of the environment (lookup table) of variable bindings, which are name associations in places where values can be stored. Each evaluation of a let
form or function call creates a new environment, expanding the current environment with new bindings. To have lambda
forms that behave like closures, an implementation presents them as a structure consisting of the function itself and the environment in which it was defined. Then the calls to this function are calculated by expanding the binding environment in which the function was defined, and not the environment in which it was called.
Older Lisps (including Emacs Lisp until recently) had a lambda
, but not a lexical scope, so although you could create anonymous functions, calls to them would be evaluated in the calling environment and not in the definition environment, and therefore there were no closures. I believe that Scheme was the first language that got this right. Sussman and Steele original Lambda papers on the implementation of the Scheme make a great mind reading for anyone who wants to understand the scope, among many other things.