Переменная в функции - PullRequest
6 голосов
/ 01 марта 2012

Я вижу следующий код ... Первый вызов (next-num) возвращает 1, а второй возвращает 2.

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

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

Что я не могу понять ... num создается let внутри next-num, это своего рода локальная переменная ... Как схема узнает, что каждый раз, когда вызывается next-num, значение num не стирается let ((num 0)); Как схема узнает, что это всегда один и тот же num, который мы модифицируем всякий раз, когда вызывается next-num?

Кажется, что num является как локальной, так и статической ... Как мы можем определить локальную переменную, но не статическую?

1 Ответ

9 голосов
/ 01 марта 2012

Это «лексическое замыкание», и вы правы, что num, «закрытая переменная» похожа на статическую переменную, например в C: она видна только коду в форме let ( это «лексическая область действия»), но она сохраняется на протяжении всего выполнения программы, а не переинициализируется при каждом вызове функции.

Я думаю, что часть, в которой вы запутались, такова: «num создается с помощью let inside next-num, это своего рода локальная переменная». Это не так, потому что блок let не является частью функции next-num: на самом деле это выражение, которое создает и возвращает функцию, которая затем связывается с next-num. (Это очень отличается, например, от C, где функции могут быть созданы только во время компиляции и путем определения их на верхнем уровне. В Scheme функции - это значения, такие как целые числа или списки, которые может возвращать любое выражение).

Вот еще один способ написать (почти) ту же вещь, которая проясняет, что define просто связывает next-num со значением выражения, возвращающего функцию:

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

Важно отметить разницу между

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

, которая делает some-var функцией, которая выполняет все expressions при вызове, и

(define some-var expression)

, который связывает some-var со значением expression, оцененным тут же. Строго говоря, предыдущая версия не нужна, потому что она эквивалентна

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

Ваш код почти такой же, как этот, с добавлением переменной лексической области num вокруг формы lambda.

Наконец, вот ключевое отличие между замкнутыми и статическими переменными, что делает замыкания намного более мощными. Если вы написали следующее:

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

тогда каждый вызов make-next-num будет создавать анонимную функцию с новой, отдельной переменной num, которая является частной для этой функции:

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

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

Это действительно крутой и мощный трюк, который объясняет большую силу языков с лексическими замыканиями.

Отредактировано, чтобы добавить: Вы спрашиваете, как Схема «знает», какую num изменить, когда вызывается next-num. В общих чертах, если не в реализации, это на самом деле довольно просто. Каждое выражение в Scheme оценивается в контексте среды (таблицы поиска) привязок переменных, которые являются ассоциациями имен с местами, которые могут содержать значения. Каждая оценка формы let или вызова функции создает новую среду, расширяя текущую среду новыми привязками. Чтобы формы lambda вели себя как замыкания, реализация представляет их как структуру, состоящую из самой функции и среды, в которой она была определена. Вызовы этой функции затем оцениваются путем расширения среды привязки, в которой была определена функция - , а не среды, в которой она была вызвана.

Более ранние Лиспы (включая Emacs Lisp до недавнего времени) имели lambda, но не лексическую область, поэтому, хотя вы могли создавать анонимные функции, вызовы к ним будут оцениваться в среде вызова, а не в среде определения, и поэтому нет закрытий. Я считаю, что Схема была первым языком, который понял это правильно. Оригинальные лямбда-бумаги от Sussman и Steele, посвященные реализации Схемы, являются отличным чтением, расширяющим кругозор, для всех, кто хочет понять область видимости, среди многих других вещей.

...