Схема назначения - PullRequest
4 голосов
/ 31 марта 2012

Когда я вычисляю следующее выражение каждый раз, когда получаю значение 10.

(((lambda (x) (lambda () (set! x (+ x 10)) x)) 0)) 

Однако я просто изменяю, абстрагируя вышеупомянутую процедуру с именем, и вызываю foo каждый раз, когда значение увеличивается на 10 !!

(define foo ((lambda (x) (lambda () (set! x (+ x 10)) x)) 0))

Кто-нибудь может объяснить, пожалуйста?

Ответы [ 2 ]

5 голосов
/ 01 апреля 2012

Функция, которую вы вызываете, представляет собой счетчик, который возвращает число на 10 больше при каждом вызове.

В первом случае, каждый раз, когда вы создаете новую функцию, а затем немедленно вызываете ее один разОтказ от функции.Таким образом, каждый раз, когда вы вызываете новый экземпляр этого счетчика в первый раз, он должен возвращать 10.

Во втором случае вы создаете функцию один раз, назначаете ее переменной и вызываете тот же самыйфункционировать повторно.Поскольку вы вызываете одну и ту же функцию, она должна вернуть 10, 20, ...

2 голосов
/ 07 апреля 2012

newacct верен, но я хотел бы вдаваться (во многие подробности), так как это то, что просто недавно поразило меня.

Я собираюсь использовать термины «окружение»и «охват» довольно свободно и означает по существу то же самое.Помните, что схема - это язык лексической области действия .

Когда схема оценивает выражение, она будет искать в своей текущей среде значения любых переменных в выражении.Если он не найдет ничего в текущей среде, он будет выглядеть в родительской среде.Если значение не находится в родительской среде, оно будет смотреть на следующем уровне вверх и так далее, пока не достигнет верхнего (глобального) уровня, где оно либо найдет значение, либо выдаст ошибку «unbound variable».

Каждый раз, когда вы звоните define, вы связываете символ со значением в таблице символов этой среды.Поэтому, если вы вызовете define на верхнем уровне, запись будет добавлена ​​в глобальную таблицу символов.Если вы вызовете define в теле процедуры, то запись будет добавлена ​​в таблицу символов этой процедуры.

Хороший способ подумать о вызове define в процедуре заключается в том, что высоздание записи в таблице символов, состоящей из параметров, тела и окружения этой процедуры.Например, процедура square будет иметь такую ​​запись:

(define a 3)

(define (square x)
    (* x x))

     GLOBAL
=================
     a-|-3
       |
square-|-{x}
       | {(* x x)}
       | {GLOBAL} ---> All the things defined on the global table

Тогда, если бы я должен был вызвать (square a), интерпретатор сначала посмотрел бы в среде, в которой определен square, иобнаружил бы, что a связано со значением 3. Затем x -> 3 в теле квадрата и процедура возвращает 9. Круто, имеет смысл.

Все становится немного сложнее, когда мы начинаем определять помощникапроцедуры внутри процедур, но все, что вам действительно нужно помнить, это то, что если он не может найти что-либо связанное с символом в текущей среде, он будет перемещаться вверх по уровням, пока не сделает этого.Кроме того, он всегда остановится на первом «матче».Поэтому, если есть локальный x, он предпочтет его по сравнению с глобальным x (скорее он будет использовать локальный x, даже не пытаясь найти глобальный).

Далее, помните, что define просто добавляет имена в таблицу символов, но set! - это мутатор, который фактически изменяет значения, с которыми связан символ.

Таким образом, (define b "blah") помещает запись в таблицу символов.b => "blah".Ничего сумасшедшего.set! изменит фактическое значение:

(set! b "foo")
b => "foo"

, но set! не может ничего добавить в таблицу.(set! c "bar") => UNBOUND VARIABLE C.

Это самое важное отличие: set! действует как любая другая процедура в том, что, если она не находит переменную в текущей области, она будет проверять постепенноболее высокие уровни, пока не найдет совпадение (или не выдаст ошибку), но define всегда добавляет привязку к области, в которой он вызывается.

Хорошо, так что вы понимаете разницу между define и set!.Хорошо.Теперь перейдем к вопросу.

Выражение (((lambda (x) (lambda () (set! x (+ x 10)) x)) 0)), как указывал newacct, будет возвращать одно и то же значение каждый раз, потому что вы каждый раз вызываете новую процедуру.Однако если вы назовете его, вы можете отслеживать среду, созданную с помощью вызова процедуры.

(define foo      <--- associated name on the symbol table
    (lambda (x)  <--- scope where x is defined
        (lambda ()            \
            (set! x (+ x 10)) |--- body
            x))               /
        0)       <--- initial value of x

Таким образом, внутренний lambda существует внутри среды, созданной первой, в которой символ x существует вначальное значение 0. Затем set! ищет запись в таблице символов для x и находит ее на следующем уровне вверх.Как только он находит запись, он изменяет ее, в этом случае добавляя 10 к любому значению, которое он там находит.Действительно крутая часть в том, что, поскольку вы связали все это с именем в глобальной таблице символов, эта среда продолжает существовать после каждого вызова!Вот почему мы можем делать классные вещи, такие как реализация объектов передачи сообщений, чтобы отслеживать данные и манипулировать ими!

Кроме того, для этой цели была создана специальная форма let, которая может быть более интуитивным способом структурирования.Это будет выглядеть так:

(define foo       <--- associated name
    (let ((x 0))  <--- scope where x is defined & initial x value
        (lambda ()            \
            (set! x (+ x 10)) |--- body
            x)))              /
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...