Подтвердить что ты не робот

Когда указывать символ в Emacs Lisp

Я начал изучать программирование с помощью Emacs Lisp. Меня так смущает символьная цитата. Например:

(progn
  (setq a '(1 2))
  (prin1 a)
  (add-to-list 'a 3)
  (prin1 a)
  (setcar a 4)
  (prin1 a)
  (push 5 a)
  ""
)

почему для функции "добавить к списку" нужен котируемый символ в качестве первого аргумента, в то время как функции "setcar" и "push" не нуждаются в котировке аргументов?

4b9b3361

Ответ 1

Здесь представлена ​​диаграмма, представляющая символ a и его значение после (setq a '(1 2)). Ячейки представляют собой элементарные структуры данных (символы и символы), а стрелки - указатели (где часть данных ссылается на другую). (Я немного упрощаю.)

 symbol                     cons              cons
+-------+----------+       +------+------+   +------+------+
|name:  |variable: |       |car:  |cdr:  |   |car:  |cdr:  |
| a     |    |     |       | 1    |  |   |   | 2    | nil  |
+-------+----|-----+       +------+--|---+   +------+------+
             |             ​↑         |       ↑
             +-------------+         +-------+

Выражение '(1 2) строит два контура справа, которые составляют список из двух элементов. Выражение (setq a '(1 2)) создает символ a, если он не существует, затем делает его "переменный слот" (часть, содержащая значение символа), указывающей на вновь созданный список. setq - встроенный макрос, а (setq a '(1 2)) - сокращение для (set 'a '(1 2)). Первый аргумент set - это символ для изменения, а второй аргумент - значение для установки слота переменной символа.

(add-to-list 'a 3) здесь эквивалентен (set 'a (cons 3 a)), потому что 3 отсутствует в списке. Это выражение делает четыре вещи:

  • Создайте новую ячейку cons.
  • Установите новое поле ячейки ячейки cons на 3.
  • Установите новое поле cdr ячейки cons в прежнее (и все еще текущее) значение a (т.е. скопируйте содержимое слота переменной a).
  • Установите переменный слот a в новую ячейку cons.

После этого вызова структуры данных выглядят следующим образом:

 symbol                     cons              cons              cons
+-------+----------+       +------+--|---+   +------+------+   +------+------+
|name:  |variable: |       |car:  |cdr:  |   |car:  |cdr:  |   |car:  |cdr:  |
| a     |    |     |       | 3    |  |   |   | 1    |  |   |   | 2    | nil  |
+-------+----|-----+       +------+--|---+   +------+--|---+   +------+------+
             |             ​↑         |       ↑         |       ↑
             +-------------+         +-------+         +-------+

Вызов setcar не создает никакой новой структуры данных и не действует на символ a, а на его значение, которое является ячейкой cons, чей car в настоящее время содержит 3. После (setcar a 4), структуры данных выглядят следующим образом:

 symbol                     cons              cons              cons
+-------+----------+       +------+--|---+   +------+------+   +------+------+
|name:  |variable: |       |car:  |cdr:  |   |car:  |cdr:  |   |car:  |cdr:  |
| a     |    |     |       | 4    |  |   |   | 1    |  |   |   | 2    | nil  |
+-------+----|-----+       +------+--|---+   +------+--|---+   +------+------+
             |             ​↑         |       ↑         |       ↑
             +-------------+         +-------+         +-------+

push - макрос; здесь (push 5 a) эквивалентно (set 'a (cons 5 a)).

setq и push являются макросами (setq является "специальной формой", что, насколько нам известно, означает макрос, определение которого встроено в интерпретатор и не представлено в Lisp), Макросы получают неопровержимые аргументы и могут выбрать их расширение или нет. set, setcar и add-to-list - это функции, которые получают свои аргументы. Оценка символа возвращает содержимое его слота переменной, например. после начального (setq a '(1 2)) значение символа a является ячейкой cons, чей автомобиль содержит 1.

Если вы все еще сбиты с толку, я предлагаю экспериментировать с (setq b a) и видеть для себя, какое из выражений изменить b, когда вы действуете на a (те, которые действуют на символ a) и которые (те, которые действуют на значение символа a).

Ответ 2

Функции оценивают свои аргументы перед выполнением, поэтому цитируйте, когда вам нужно передать фактический символ (например, как указатель на некоторую структуру данных) и не указывать, когда это значение переменной.

add-to-list выполняет внутреннюю мутацию своего первого аргумента, поэтому ему нужен кавычек.

push не является функцией, а макросом; поэтому он может принимать неуказанные аргументы без оценки. Встроенные формы, такие как setcar, также не имеют этого ограничения.

Ответ 3

Другие ответы, даваемые до сих пор, разъясняют использование quote и разницу между функциями, с одной стороны, и макросами и специальными формами, с другой стороны.

Однако они не доходят до другой части вопроса: почему add-to-list как есть? Почему для первого аргумента требуется символ? Это отдельный вопрос, независимо от того, оценивает ли он аргумент. Это реальный вопрос, стоящий за дизайном add-to-list.

Можно предположить, что add-to-list оценил свои аргументы и ожидал, что значение первого arg будет списком, а затем добавит значение второго arg в этот список как элемент и вернет результат (новый список или тот же список). Это позволит вам сделать (add-to-list foo 'shoe), чтобы добавить символ shoe в список, который является значением foo - say (1 2 buckle) -, чтобы дать (1 2 buckle shoe).

Дело в том, что такая функция не очень полезна. Зачем? Потому что значение списка не обязательно доступно. Переменная foo может рассматриваться как способ доступа к ней - "дескриптор" или "указатель" к ней. Но это неверно для списка, возвращаемого функцией. Этот возвращенный список может состоять из новой структуры списка, и обычно нет ничего (без переменной), указывающего на этот список. Функция add-to-list никогда не видит символ (переменную) foo - он не знает, что значение списка, которое он получает в качестве первого аргумента, привязано к foo. Если add-to-list были спроектированы таким образом, вам все равно придется назначать возвращаемый результат переменной списка.

IOW, add-to-list оценивает свои аргументы, потому что это функция, но это не объясняет многое. Он ожидает символ как значение его первого аргумента. И он ожидает, что значение этой переменной (символа) будет списком. Он добавляет значение своего второго аргумента в список (возможно, меняет структуру списка), и он устанавливает значение переменной, которое является значением его первого аргумента для этого списка.

Нижняя строка: ему нужен символ как arg, потому что его задание назначает новое значение этому символу (новое значение является тем же самым значением списка или тем же значением с добавленным новым элементом списка на передней панели).

И да, другим способом было бы использовать макрос или специальную форму, как в push. Это та же идея: push хочет, чтобы символ был вторым аргументом. Разница в том, что push не оценивает свои аргументы, поэтому символ не нужно указывать. Но в обоих случаях (в Emacs Lisp) код должен получить символ, чтобы установить его значение в расширенный список.