Краткий ответ
Обходите правила оценки по умолчанию и выполняйте , а не оценку выражения (символ или s-exp), передавая его функции точно в том виде, в котором оно было напечатано.
Длинный ответ: правило оценки по умолчанию
Когда вызывается обычная (я вернусь к этому позже) функция, все передаваемые ей аргументы оцениваются. Это означает, что вы можете написать это:
(* (+ a 2)
3)
Что, в свою очередь, оценивает (+ a 2)
, оценивая a
и 2. Значение символа a
ищется в текущем наборе привязок переменной, а затем заменяется. Скажем, a
в настоящее время привязан к значению 3:
(let ((a 3))
(* (+ a 2)
3))
Мы получили бы (+ 3 2)
, + затем вызывается на 3 и 2, получая 5. Наша первоначальная форма теперь (* 5 3)
, получая 15.
Объясните quote
Уже!
Хорошо. Как показано выше, все аргументы функции оцениваются, поэтому, если вы хотите передать символ a
, а не его значение, вы не хотите его оценивать. Символы Лиспа могут удваиваться как в качестве их значений, так и в качестве маркеров, когда вы в других языках использовали бы строки, например ключи к хеш-таблицам.
Вот тут и приходит quote
. Предположим, вы хотите построить график распределения ресурсов из приложения Python, а лучше сделать это в Lisp. Сделайте, чтобы ваше приложение Python сделало что-то вроде этого:
print("'(")
while allocating:
if random.random() > 0.5:
print(f"(allocate {random.randint(0, 20)})")
else:
print(f"(free {random.randint(0, 20)})")
...
print(")")
Вывод, который выглядит примерно так (слегка симпатично):
'((allocate 3)
(allocate 7)
(free 14)
(allocate 19)
...)
Помните, что я сказал о quote
("галочка"), в результате чего правило по умолчанию не применяется? Хорошо. В противном случае произойдет поиск значений allocate
и free
, и мы этого не хотим. В нашем Лиспе мы хотим сделать:
(dolist (entry allocation-log)
(case (first entry)
(allocate (plot-allocation (second entry)))
(free (plot-free (second entry)))))
Для данных, приведенных выше, была бы выполнена следующая последовательность вызовов функций:
(plot-allocation 3)
(plot-allocation 7)
(plot-free 14)
(plot-allocation 19)
А как же list
?
Ну, иногда вы делаете хотите оценить аргументы. Скажем, у вас есть изящная функция, управляющая числом и строкой и возвращающая список полученных ... вещей. Давайте сделаем фальстарт:
(defun mess-with (number string)
'(value-of-number (1+ number) something-with-string (length string)))
Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))
Эй! Это не то, что мы хотели. Мы хотим выборочно оценить некоторые аргументы и оставить другие как символы. Попробуйте №2!
(defun mess-with (number string)
(list 'value-of-number (1+ number) 'something-with-string (length string)))
Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)
Не только quote
, но backquote
Намного лучше! Кстати, этот шаблон настолько распространен в (в основном) макросах, что для этого есть специальный синтаксис. Обратная цитата:
(defun mess-with (number string)
`(value-of-number ,(1+ number) something-with-string ,(length string)))
Это похоже на использование quote
, но с возможностью явной оценки некоторых аргументов, ставя их перед запятой. Результат эквивалентен использованию list
, но если вы генерируете код из макроса, вам часто нужно оценивать только небольшие части возвращаемого кода, поэтому обратная цитата больше подходит. Для более коротких списков list
может быть более читабельным.
Эй, ты забыл про quote
!
Так, где это оставляет нас? Ах да, что на самом деле делает quote
? Он просто возвращает свой аргумент (ы) без оценки! Помните, что я сказал в начале о регулярных функциях? Оказывается, что некоторые операторы / функции должны , а не оценивать свои аргументы. Например, IF - вы бы не хотели, чтобы ветвь else оценивалась, если она не была взята, верно? Так работают так называемые специальные операторы вместе с макросами. Специальные операторы также являются «аксиомой» языка - минимальным набором правил - по которым вы можете реализовать остальную часть Lisp, комбинируя их вместе различными способами.
Вернуться к quote
, хотя:
Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL
Lisp> 'spiffy-symbol ; ' is just a shorthand ("reader macro"), as shown above
SPIFFY-SYMBOL
Сравнить с (на Сталь-Банк Common Lisp):
Lisp> spiffy-symbol
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "initial thread" RUNNING {A69F6A9}>:
The variable SPIFFY-SYMBOL is unbound.
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-INT:SIMPLE-EVAL-IN-LEXENV SPIFFY-SYMBOL #<NULL-LEXENV>)
0]
Потому что в текущей области видимости spiffy-symbol
нет!
Подведение итогов
quote
, backquote
(с запятой) и list
- это некоторые из инструментов, которые вы используете для создания списков, которые не только являются списками значений, но, как вы видели, могут использоваться как легкие (не нужно) определить struct
) структуры данных!
Если вы хотите узнать больше, я рекомендую книгу Питера Сейбела Practical Common Lisp для практического подхода к изучению Lisp, если вы уже в программировании в целом. Со временем в вашем путешествии по Лиспу вы тоже начнете использовать пакеты. Руководство идиота по пакетам Common Lisp Рона Гаррета даст вам хорошее объяснение этого.
Счастливого взлома!