问题
I have see the following code... The first call of (next-num)
returns 1
, and the second returns 2
.
(define next-num
(let ((num 0))
(lambda () (set! num (+ num 1)) num)))
(next-num) ; 1
(next-num) ; 2
What I can not understand is... num
is created by let
inside next-num
, it is kind of a local variable... How does scheme know that each time next-num
is called, the value of num
is not erased by let ((num 0))
; How does scheme know that it is always the same num
that we modify whenever next-num
is called?
It seems that num
is both local and static... How can we define a local variable, but not static?
回答1:
This is "lexical closure" and you're right that num
, the "closed-over variable" is similar to a static variable, in C for example: it's only visible to code within the let
form (its "lexical scope"), but it persists across the whole program run, rather than being re-initialized with each call to the function.
I think the part you're confused on is this: "num
is created by let inside next-num
, it is kind of a local variable". This isn't true because the let
block isn't part of the next-num
function: it's actually an expression which creates and returns the function which is then bound to next-num
. (This is very different, e.g., from C, where functions can only be created at compile-time and by defining them at top-level. In Scheme, functions are values like integers or lists, which any expression can return).
Here's another way to write (almost) the same thing which makes it clearer that the define
is just associating next-num
to the value of a function-returning expression:
(define next-num #f) ; dummy value
(let ((num 0))
(set! next-num
(lambda () (set! num (+ num 1)) num)))
It's important to note the difference between
(define (some-var args ...) expression expression ...)
which makes some-var
a function which executes all the expressions
when called, and
(define some-var expression)
which binds some-var
to the value of expression
, evaluated then and there. Strictly speaking, the former version is unnecessary, because it's equivalent to
(define some-var
(lambda (args ...) expression expression ...))
Your code is almost the same as this, with the addition of the lexically scoped variable, num
, around the lambda
form.
Finally, here's a key difference between closed-over variables and static variables, which makes closures much more powerful. If you had written the following instead:
(define make-next-num
(lambda (num)
(lambda () (set! num (+ num 1)) num)))
then each call to make-next-num
would create an anonymous function with a new, distinct num
variable, which is private to that 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 which accounts for a lot of the power of languages with lexical closures.
Edited to add: You ask how Scheme "knows" which num
to modify when next-num
is called. In outline, if not in implementation, this is actually pretty simple. Every expression in Scheme is evaluated in the context of an environment (a lookup table) of variable bindings, which are associations of names to places which can hold values. Each evaluation of a let
form or a function call creates a new environment by extending the current environment with new bindings. To arrange to have lambda
forms behave as closures, the implementation represents them as a structure consisting of the function itself plus the environment in which it was defined. Calls to that function are then evaluated by extending the binding environment in which the function was defined -- not the environment in which it was called.
Older Lisps (including Emacs Lisp until recently) had lambda
, but not lexical scope, so although you could create anonymous functions, calls to them would be evaluated in the calling environment rather than the definition environment, and so there were no closures. I believe Scheme was the first language to get this right. Sussman and Steele's original Lambda Papers on the implementation of Scheme make great mind-expanding reading for anyone who wants to understand scoping, among many other things.
来源:https://stackoverflow.com/questions/9517366/variable-in-a-function