привязка значений к кадрам в модели среды - PullRequest
0 голосов
/ 08 декабря 2018

Я немного запутался в том, как работает модель среды оценки, и надеюсь, что кто-то может объяснить.

SICP говорит:

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

Первый пример:

Если I:

(define y 5)

в глобальной среде, затем вызовите

(f y)

где

(define (f x) (set! x 1))

Мы создаем новую среду (e1).В пределах e1 x будет связан со значением y (5).В теле значение x теперь будет равно 1. Я обнаружил, что y по-прежнему равен 5. Я считаю, что причина этого в том, что x и y расположены в разных кадрах.То есть я полностью заменил значение х.Я изменил фрейм, где х связан, а не только его значение.Это правильно?

Второй пример:

Если мы имеем в глобальной среде:

(define (cons x y)
  (define (set-x! v) (set! x v))
  (define (set-y! v) (set! y v))
  (define (dispatch m)
    (cond ((eq? m 'car) x)
          ((eq? m 'cdr) y)
          ((eq? m 'set-car!) set-x!)
          ((eq? m 'set-cdr!) set-y!)
          (else (error "Undefined 
                 operation: CONS" m))))
  dispatch)

(define (set-car! z new-value)
  ((z 'set-car!) new-value)
  z)

Теперь я говорю:

(определить z2 (cons 1 2))

Предположим, что z2 имеет значение процедуры отправки в среде с именем e2, и я вызываю:

(set-car! z2 3)

Set-car!создает новую среду e3.Внутри e3 параметр z связан со значением z2 (процедура отправки в e2), как в моем первом примере.После того, как тело выполнено, z2 теперь '(3 2).Я думаю поставил машину!работает так, как работает, потому что я изменяю состояние объекта, содержащегося в z (на который также ссылается z2 в global), но не заменяю его.То есть я не модифицировал фрейм, где привязан z.

Во втором примере кажется, что z2 в глобальном и z в e3 являются общими.Я не уверен насчет моего первого примера.Основываясь на правилах применения процедур в модели среды, кажется, что x и y совместно используются, хотя это совершенно невозможно обнаружить, потому что 5 не имеет локального состояния.

Все ли я сказал правильно?Я неправильно понял цитату?

Ответы [ 3 ]

0 голосов
/ 10 декабря 2018

TL; DR: простые значения в Scheme неизменны, копируются полностью при передаче в качестве аргументов в функции.Составные значения являются изменяемыми и передаются как копия указателя, тогда как скопированный указатель указывает на то же место в памяти, что и исходный указатель.


То, с чем вы боретесь, известно как «мутация»».Простые значения, такие как 5 , являются неизменяемыми.Нет "set-int!", чтобы изменить 5, чтобы впредь содержать значение 42 в нашей программе.И хорошо, что нет.

Но значение переменной является изменчивым.Переменная является привязкой в ​​кадре вызова функции, и ее можно изменить с помощью set!.Если у нас есть

(define y 5)
(define (foo x) (set! x 42) (display (list x x)))
(foo 5)
   --> foo is entered
       foo invocation environment frame is created as { x : {int 5} }
       x's binding's value is changed: the frame is now { x : {int 42} }
       (42 42)    is displayed
       y still refers to 5 in the global environment

Но если foo получает значение, которое само содержит изменяемые ссылки, которые могут быть видоизменены, то есть изменены "на месте", то хотя сам кадр foo не изменяется,значение, на которое ссылается привязка в нем, может быть.

(define y (cons 5 6))     ; Scheme's standard cons
   --> a cons cell is created in memory, at {memory-address : 123}, as
                   {cons-cell {car : 5} {cdr : 6} } 
(define (foo x) (set-car! x 42) (display (list x x)))
(foo y)
   --> foo is entered
       foo invocation environment frame is created as 
             { x : {cons-cell-reference {memory-address : 123}} }
       x's binding's value is *mutated*: the frame is still
             { x : {cons-cell-reference {memory-address : 123}} }
           but the cons cell at {memory-address : 123} is now
                   {cons-cell {car : 42} {cdr : 6} } 
       ((42 . 6) (42 . 6))    is displayed
       y still refers to the same binding in the global environment
         which still refers to the same memory location, which has now 
         been altered in-place: at {memory-address : 123} is now
                   {cons-cell {car : 42} {cdr : 6} } 

В схеме cons - это примитив, который создает изменяемые cons-ячейки, которые можно изменить на месте с помощью set-car! и set-cdr!.

Эти упражнения SICP намереваются показать, что нет необходимости использовать его в качестве примитивной встроенной процедуры;что он может быть реализован пользователем, даже если он не был встроен в схему.Для этого достаточно set!.


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

Но если я "упаковываю" его и передаю (list 5) в какую-либо функцию, значение, которое копируется - в Lisp - является указателем на этот "ящик".Это называется «значением передачи по указателю» или чем-то в этом роде.

Если функция изменяет значение этого поля на (set-car! ... 42), оно будет изменено на месте, и отныне у меня будет 42 в этом поле, (list 42) - в том же месте памяти, что и раньше.Привязка фрейма моей среды останется неизменной - он все равно будет ссылаться на тот же объект в памяти - но само значение будет изменено, изменено на месте, изменено.

Это работает, потому что блок является составным элементом данных,Независимо от того, поместил я в него простое или составное значение, само поле (то есть изменяемая ячейка cons) не является простым, поэтому будет передаваться по значению указателя - будет скопирован только указатель, а не то, на что он указывает.

0 голосов
/ 13 декабря 2018

x, привязанный к значению y, означает, что x - это новая привязка, которая получает копию того же значения, что и y.x и y не являются псевдонимами для общей памяти.

Хотя из-за проблем оптимизации привязки не являются точными ячейками памяти, вы можете смоделировать их поведение таким образом.Другими словами, вы можете рассматривать среду как мешок мест хранения, обозначаемых символами.

Оценщики образовательных схем в схеме, фактически, используют списки ассоциаций для представления сред.Таким образом, (let ((x 1) (y 2)) ...) создает среду, которая выглядит просто как ((y . 1) (x . 2)).Места хранения - это поля cdr пар cons в этом списке, а их метки - это символы в полях car.Сама клетка является связующим;символ и местоположение связаны друг с другом благодаря тому, что находятся в одной и той же структуре cons.

Если вокруг этого let существует внешняя среда, то эти пары ассоциаций могут быть просто добавлены в нее с помощью cons:

(let ((z 3))
  ;; env is now ((z . 3))
  (let ((x 1) (y 2))
     ;; env is now ((y . 2) (x . 1) (z . 3))

Среда - это просто набор привязок, на которые мы нажимаем.Когда мы фиксируем лексическое замыкание, мы просто берем текущий указатель и прячем его в объект замыкания.

(let ((z 3))
  ;; env is now ((z . 3))
  (let ((x 1) (y 2))
     ;; env is now ((y . 2) (x . 1) (z . 3))
     (lambda (a) (+ x y z a))
     ;; lambda is an object with these three pices:
     ;; - the environment ((y . 2) (x . 1) (z . 3))
     ;; - the code (+ x y z a)
     ;; - the parameter list (a)
     )
  ;; after this let is done, the environment is again ((z . 3))
  ;; but the above closure maintains the captured one
)

Итак, предположим, что мы вызываем lambda с аргументом 10. Лямбда принимает список параметров (a) и связывает его со списком аргументов для создания новой среды:

((a . 1))

Эта новая среда не создана в вакууме;он создается как расширение захваченной среды.Итак, действительно:

((a . 1) (y . 2) (x . 1) (z . 3))

Теперь, в этой эффективной среде, выполняется тело (+ x y z a).

Все, что вам нужно понять о средах, можно понять применительно к этой паре минусов.модель привязок.

Присвоение переменной?Это просто set-cdr! для связывания на основе минусов.

Что такое "расширение среды"?Это просто выдвигает основанную на минусах привязку на фронт.

Что такое «свежая привязка» переменной?Это просто выделение новой ячейки с (cons variable-symbol value) и расширение с ней среды путем ее включения.

Что такое «затенение» переменной?Если среда содержит (... ((a . 2)) ...), и мы помещаем новую привязку (a . 3) в эту среду, то эта a теперь видима, а (a . 2) скрыта просто потому, что функция assoc выполняет линейный поиск и находит (a . 2)первый!Подбор внутренней и внешней среды идеально моделируется assoc.Внутренние привязки появляются слева от внешних привязок, ближе к началу списка и находятся первыми.

Семантика совместного использования всех вытекает из семантики этих списков ячеек.В модели списка ассоциаций совместное использование среды происходит, когда два списка ассоциаций среды совместно используют один и тот же хвост.Например, каждый раз, когда мы называем нашу лямбду выше, создается новая среда аргументов (a . whatever), но она расширяет тот же хвост захваченной среды.Если лямбда изменится a, это не будет видно из других вызовов, но если она изменит x, то другие вызовы увидят ее.a является частным для лямбда-вызова, но x, y и z являются внешними по отношению к лямбде в захваченной среде.

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

Реальные реализации в основном просто оптимизируются вокруг этого.например, переменная, которая инициализируется из константы типа 42 и никогда не назначается, вообще не должна существовать как фактическая запись среды;оптимизация, называемая «постоянным распространением», может просто заменить вхождения этой переменной на 42, как если бы это был макрос.Реальные реализации могут использовать хеш-таблицы или другие структуры для уровней среды, а не связанные списки.Реальные реализации могут быть скомпилированы: лексические среды могут быть скомпилированы в соответствии с различными стратегиями, такими как «преобразование замыкания».По сути, вся лексическая область видимости может быть сведена в один вектороподобный объект.Когда замыкание выполняется во время выполнения, весь вектор дублируется и инициализируется.Скомпилированный код ссылается не на переменные символы, а на смещения в векторе замыкания, что значительно быстрее: линейный поиск по списку ассоциаций не требуется.

0 голосов
/ 08 декабря 2018

Чтобы ответить на ваш первый вопрос: предполагая, что вы хотели написать (f y) в своем первом вопросе, а не (f 5), причина того, что у не изменилось, состоит в том, что ракетка (как и большинство языков) является «вызовом по значению»язык.То есть значения передаются в вызовы процедур.В этом случае аргумент y оценивается как 5 до вызова f.Отключение привязки x не влияет на привязку y.

Чтобы ответить на ваш второй вопрос: во втором примере есть общие среды.То есть z - это функция, которая закрыта для среды (вы назвали ее e2).Каждый вызов z создает новую среду, связанную с существующей средой e2.Выполнение мутации в x или y в этой среде влияет на все будущие ссылки на среду e2.

Резюме: передача значения переменной отличается от передачи закрытия, содержащего эту переменную.Если я скажу

(f y)

... после завершения вызова 'y' будет по-прежнему ссылаться на то же значение [*].Если я напишу

f (lambda (...) ... y ...)

(то есть, передавая закрытие, которое имеет ссылку на y, тогда y может быть связано с другим значением после вызова f.

Если вас это смущает, вы не одиноки. Ключ в следующем: не прекращайте использовать замыкания. Вместо этого прекращайте использовать мутации.

[*], если yявляется изменяемым значением, оно может быть видоизменено, но все равно будет «тем же» значением. См. примечание выше о путанице.

...