Common Lisp, ссылка на значение и фактическое значение

#macros #reference #common-lisp

#макросы #ссылка #common-lisp

Вопрос:

Рассмотрим этот фрагмент кода:

 (defvar lst '(1 1))

(defmacro get-x (x lst)
  `(nth ,x ,lst))

(defun get-y (y lst)
  (nth y lst))
  

Теперь давайте предположим, что я хочу изменить значение элементов списка с именем lst, car с помощью get-x и cdr с помощью get-y.
Когда я пытаюсь изменить значение с помощью get-x (с помощью setf), все идет нормально, но если я попробую это с помощью get-y, это сигнализирует об ошибке (сокращено):

; пойманный СТИЛЬ -ПРЕДУПРЕЖДЕНИЕ: ; неопределенная функция: (SETF GET-STUFF)

Почему это происходит?

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

Прав ли я в своих подозрениях? Если я прав, то как узнать, что является просто ссылкой на значение и фактическим значением?

Ответ №1:

Ошибка не возникает с версией макроса, потому что, как вы предполагали, выражение (setf (get-x some-x some-list) some-value) будет расширено (во время компиляции) во что-то вроде (setf (nth some-x some-list) some-value) (не совсем, но детали setf расширения сложны), и компилятор знает, как с этим справиться (т. Е. Для функции setf определен подходящий nth расширитель).

Однако в случае get-y компилятор не имеет setf расширителя, если вы его не предоставите. Самый простой способ сделать это был бы

 (defun (setf get-y) (new-value x ls)    ; Note the function's name: setf get-y 
    (setf (nth x ls) new-value))
  

Обратите внимание, что существует несколько соглашений, касающихся setf -расширителей:

  1. Новое значение всегда предоставляется в качестве первого аргумента setf функции
  2. Предполагается, что все setf функции должны возвращать новое значение в качестве результата (поскольку это то, что должна возвращать вся setf форма)

Кстати, в Common Lisp нет такого понятия, как «ссылка» (по крайней мере, не в смысле C ), хотя когда-то были диалекты Lisp, у которых были локали. Обобщенные формы размещения (т.Е. setf и их механизмы) работают совсем не так, как обычные ссылки в стиле C . Посмотрите CLHS, если вам интересны подробности.

Комментарии:

1. На самом деле я до сих пор не верил, что в Common Lisp существуют ссылки в смысле C , спасибо, что прояснили это. Мне нужно было найти объяснение моей проблемы, и вот что я придумал. Вся эта история с определением функции с именем, подобным SETF GET-Y, для меня в некотором роде нова как концепция… В любом случае, я проведу еще несколько исследований по этому поводу, спасибо за вашу помощь!

Ответ №2:

SETF — это макрос.

Идея заключается в том, что для установки и чтения элементов из структур данных выполняются две операции, но обычно требуются два разных имени (или, возможно, даже что-то более сложное). SETF теперь позволяет использовать только одно имя для обоих:

 (get-something x)
  

Выше приведена структура данных. Тогда обратное просто равно:

 (setf (get-something x) :foobar)
  

Выше структура данных устанавливается в X с помощью :FOOBAR.

SETF не обрабатывает (get-something x) как ссылку или что-то в этомроде. У него просто есть база данных обратных операций для каждой операции. Если вы используете GET-SOMETHING, он знает, что такое обратная операция.

Откуда SETF это знает? Просто: вы должны сообщить это.

Для n-й операции SETF знает, как задать n-й элемент. Это встроено в Common Lisp.

Для вашей собственной операции GET-Y в SETF нет этой информации. Вы должны сообщить это. Смотрите ГиперСпек Common Lisp для примеров. Одним из примеров является использование DEFUN и (SETF GET-Y) в качестве имени функции.

Также обратите внимание на следующие проблемы со стилем в вашем примере:

  • lst — неподходящее имя для переменной DEFVAR. Используйте *list * в качестве имени, чтобы было ясно, что это специальная переменная, объявленная DEFVAR (или аналогичная).

  • ‘(1 2) является литеральной константой. Если вы пишете программу на Common Lisp, последствия ее изменения не определены. Если вы хотите изменить список позже, вам следует использовать LIST или что-то вроде COPY-LIST.

Комментарии:

1. Да, я знаю, что мне действительно следует использовать звездочки вокруг глобальных переменных и так далее, я просто был очень ленив и хотел написать то, что я хотел, кратчайшим возможным способом, мой «реальный» код выглядит не так. Спасибо за ваше объяснение!