Как изменить код в лямбде, сохраняя захваченные переменные? - PullRequest
3 голосов
/ 30 октября 2019

Допустим, у меня есть REPL для процесса, выполняющего мой код Common Lisp. Возможно, запущен SWANK / SLIME.

Я хочу обновить функцию, определенную с помощью defun , в моем текущем процессе. Функция, возможно, захватила некоторые переменные в привязке let. По сути, эта функция является замыканием.

Как я могу обновить код в этом замыкании без потери данных, которые она захватила?

2019-11-03:Я выбрал один ответ ниже, но я рекомендую прочитать все ответы. У каждого есть интересное понимание.

Ответы [ 3 ]

4 голосов
/ 30 октября 2019

По сути, вы не можете, и это одна из причин, по которой следует избегать использования DEFUN в LET.

Можно создать новое замыкание и попытаться скопировать состояние из старого в новое.

Одной из проблем является то, что переносимый Common Lisp не обеспечивает большого динамического доступа к замыканиям. Нельзя «пойти» в замыкание и добавить что-то или заменить что-то извне. Для замыканий не определены рефлексивные или интроспективные операции.

Таким образом, все, что вы позже хотите сделать с замыканием, должно уже присутствовать в коде, генерирующем замыкание.

Допустим, у вас есть этот код:

(let ((foo 1))
  (defun add (n)
    n))

Теперь мы решили, что add неверно и должны что-то добавить?

Нам нужен эффект этого:

(let ((foo 1))
   (defun add (n)
     (+ n foo)))

Как мы можемизменить оригинал? Мы в принципе не можем.

Если бы у нас было:

(let ((foo 1))
  (defun get-foo ()
    foo)
  (defun add (n)
    n))

Мы могли бы сделать:

(let ((ff (symbol-function 'get-foo))
      (fa (symbol-function 'add)))
  (setf (symbol-function 'add)
     (lambda (n)
       (+ (funcall fa n) (funcall ff)))))

Это тогда определяет новую функцию add, которая имеет доступк значениям замыкания с помощью старых функций - захваченных в собственном замыкании.

Стиль

Не использовать DEFUN, заключенные в LET:

  • их трудно изменить
  • компилятор не видит DEFUN как формы верхнего уровня
  • эффекты трудно контролировать
  • что мы хотим сделать с загрузкой кода несколькораз?
  • их сложно отладить
  • У Common Lisp есть способы справиться с глобальным состоянием.
4 голосов
/ 30 октября 2019

Снаружи нельзя.

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

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

Может быть уместно (из https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html):

Достопочтенный мастер Qc Na шел со своим учеником Антоном. Надеясь пригласить мастера к дискуссии, Антон сказал: «Учитель, я слышал, что предметы - это очень хорошая вещь - это правда? Qc Na с жалостью посмотрел на своего ученика и ответил: «Глупый ученик - объекты - это просто укрытия бедняков».

Наказанный, Антон отошел от своего хозяина и вернулся в свою камеру, намереваясь изучать укупорки. Он внимательно прочитал всю серию статей «Лямбда: предельное ...» и ее двоюродных братьев и реализовал небольшой интерпретатор Scheme с системой объектов на основе замыканий. Он многому научился и с нетерпением ждал, чтобы сообщить своему хозяину о своем прогрессе.

На следующей прогулке с Qc Na Антон попытался произвести впечатление на своего хозяина, сказав: «Учитель, ямы усердно изучали этот вопрос и теперь понимаем, что предметы действительно являются замыканиями бедняков ». Qc Na ответил, ударив Антона палкой, сказав:« Когда ты научишься? Затворы - это предмет бедняков. В этот момент Антон стал просветленным.

3 голосов
/ 30 октября 2019

Другие ответы объясняют, что вы не можете делать то, что вы ищете: вот некоторые практические причины почему вы не можете.

Рассмотрим фрагмент кода, подобный следующему:

(let ((a 1) (b 3) (c 2))
  (lambda (x)
    (+ (* a x x) (* b x) c)))

Давайте представим, что это компилируется: что будет делать разумный компилятор? Ну, вполне очевидно, что это может превратить это в следующее:

(lambda (x)
  (+ (* 1 x x) (* 3 x) 2)

(а затем, вероятно, в

(lambda (x)
  (+ (* x x) (* 3 x) 2))

и, возможно, дальше в

(lambda (x)
  (+ (* x (+ x 3)) 2))

), прежде чем, наконец,

Ни одно из этих преобразований тела функции не ссылается ни на одну из лексических привязок, введенных замыканием. Вся среда была скомпилирована.

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

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

(defun make-box (contents)
  (values
   (lambda ()
     contents)
   (lambda (new)
     (setf contents new))))

make-box возвращает две функции: читатель и писатель, которые закрывают содержимое поля. И это общее состояние не может быть полностью скомпилировано.

Но нет абсолютно никакой причины, почему закрытое состояние, например, все еще знает, что закрываемая переменная была вызвана contents после make-box было скомпилировано (или даже раньше). Например, обе функции могут ссылаться на некоторый вектор лексического состояния, и обе знают, что в источнике contents есть первый элемент этого вектора. Все имена пропали, поэтому было бы невозможно заменить одну из функций, разделяющих это состояние, на другую.

Кроме того, в реализациях с различными компиляторами и интерпретаторами вообще нет причин, почему интерпретатордолжен иметь общее представление закрытого лексического состояния с компилятором (и компилятор, и интерпретатор могут иметь несколько разных представлений). И на самом деле спецификация CL решает эту проблему - запись для compile говорит:

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

и эта оговорка предназначена для того, чтобы иметь дело со случаями, подобными этому: если у вас есть набор функций, которые разделяют некоторое лексическое состояние, то, если компилятор имеетотличается представление лексического состояния от интерпретатора, составление только одного из них проблематично. (Я обнаружил это, пытаясь сделать что-то ужасное с общим лексическим состоянием на D-машине примерно в 1989 году, и какой-то добрый член комитета объяснил мне мою путаницу.)


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

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

Оба этих случая действительно говорят о том, что реализации языка либо должны принимать довольно низкую производительность, либо требуют героических приемов, и в любом случае было бы больше возможностей для реализации, чем уже было. Ну, одна из целей усилий Common Lisp заключалась в том, что, хотя героические приемы разрешены, они не должны требовать для высокой производительности. Кроме того, язык уже считался достаточно большим. Наконец, разработчики почти наверняка просто отклонили бы такое предложение: у них уже было достаточно работы, и они больше не хотели, особенно на такого рода уровне «вам нужно будет полностью перестроить компилятор, чтобы сделать это».

Вот почему прагматично то, что вы ищете, невозможно.

...