Variable in a function

故事扮演 提交于 2020-01-02 03:30:11

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!