#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
-расширителей:
- Новое значение всегда предоставляется в качестве первого аргумента
setf
функции - Предполагается, что все
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. Да, я знаю, что мне действительно следует использовать звездочки вокруг глобальных переменных и так далее, я просто был очень ленив и хотел написать то, что я хотел, кратчайшим возможным способом, мой «реальный» код выглядит не так. Спасибо за ваше объяснение!