2012-03-01 12 views
6

He visto el siguiente código ... La primera llamada de (next-num) devuelve 1, y la segunda devuelve 2.Variable en una función

(define next-num 
    (let ((num 0)) 
    (lambda() (set! num (+ num 1)) num))) 

(next-num) ; 1 
(next-num) ; 2 

Lo que no entiendo es ... num es creado por el interior letnext-num, que es una especie de una variable local ... ¿Cómo sabe esquema que cada vez que se llama next-num, el valor de num es no borrado por let ((num 0)); ¿Cómo sabe el esquema que siempre es el mismo num que modificamos cada vez que se llama al next-num?

Parece que num es tanto local como estático ... ¿Cómo podemos definir una variable local, pero no estática?

Respuesta

9

Este es el "cierre léxica" y tienes razón en que num, el "cerrado sobre la variable" es similar a una variable estática, en C, por ejemplo: que es sólo visible para el código dentro de la forma let (su "léxico alcance "), pero persiste en todo el programa, en lugar de reiniciarse con cada llamada a la función.

Creo que la parte en la que estás confundido es esto: "num es creado por let dentro de next-num, es una especie de variable local". Esto no es cierto porque el bloque let no es parte de la función next-num: en realidad es una expresión que crea y devuelve la función que luego está vinculada a next-num. (Esto es muy diferente, por ejemplo, de C, donde las funciones solo se pueden crear en tiempo de compilación y definiéndolas en el nivel superior. En Scheme, las funciones son valores como enteros o listas, que cualquier expresión puede devolver).

Ésta es otra manera de escribir (casi) la misma cosa que hace que sea más claro que el define se acaba asociando next-num al valor de una expresión de retorno de la función:

(define next-num #f) ; dummy value 
(let ((num 0)) 
    (set! next-num 
     (lambda() (set! num (+ num 1)) num))) 

Es importante tener en cuenta la diferencia entre

(define (some-var args ...) expression expression ...) 

que hace some-var una función que ejecuta todo el expressions cuando se le llama, y ​​

(define some-var expression) 

que enlaza some-var con el valor de expression, evaluado en ese momento. Estrictamente hablando, la versión anterior es innecesaria, porque es equivalente a

(define some-var 
    (lambda (args ...) expression expression ...)) 

su código es casi lo mismo que esto, con la incorporación de la variable de ámbito léxico, num, alrededor de la forma lambda.

Finalmente, aquí hay una diferencia clave entre las variables cerradas y las variables estáticas, lo que hace que los cierres sean mucho más potentes.Si hubiera escrito lo siguiente en su lugar:

(define make-next-num 
    (lambda (num) 
    (lambda() (set! num (+ num 1)) num))) 

continuación, cada llamada a make-next-num crearía una función anónima con una nueva, distinta num variable, que es privado para esa función:

(define f (make-next-num 7)) 
(define g (make-next-num 2)) 

(f) ; => 8 
(g) ; => 3 
(f) ; => 9 

Esta es una truco realmente genial y poderoso que explica gran parte del poder de los lenguajes con cierres léxicos.

Editado para agregar: Usted pregunta cómo Scheme "sabe" qué num se modifica cuando se llama next-num. En resumen, si no en la implementación, esto es bastante simple. Cada expresión en Scheme se evalúa en el contexto de un entorno (una tabla de búsqueda) de enlaces de variables, que son asociaciones de nombres para lugares que pueden contener valores. Cada evaluación de un formulario let o una llamada a función crea un nuevo entorno al ampliar el entorno actual con nuevas vinculaciones. Para disponer que los formularios lambda se comporten como cierres, la implementación los representa como una estructura que consta de la función misma más el entorno en el que se definió. Las llamadas a esa función se evalúan extendiendo el entorno de enlace en el que se definió la función: no el entorno en el que se llamó.

Lisp anteriores (incluido Emacs Lisp hasta hace poco) tenían lambda, pero no alcance léxico, por lo que aunque podría crear funciones anónimas, las llamadas se evaluarían en el entorno de llamada en lugar del entorno de definición, por lo que no cierres Creo que Scheme fue el primer idioma en hacerlo bien. La versión original de Sussman y Steele, Lambda Papers, sobre la implementación de Scheme, es una excelente lectura que amplía la mente para cualquiera que desee comprender el alcance, entre muchas otras cosas.

Cuestiones relacionadas