Пройдя через основные части вводной книги Lisp, я все еще не мог понять, что делает специальный оператор (quote)
(или эквивалентный '
), но это было во всем Lisp код, который я видел.
Что он делает?
Пройдя через основные части вводной книги Lisp, я все еще не мог понять, что делает специальный оператор (quote)
(или эквивалентный '
), но это было во всем Lisp код, который я видел.
Что он делает?
Краткий ответ Обходите правила оценки по умолчанию и не оценивайте выражение (символ или 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
а не его значение, вы не хотите его оценивать. Символы Lisp могут удваиваться как в качестве их значений, так и в качестве маркеров, когда вы в других языках использовали бы строки, например ключи к хеш-таблицам.
Вот тут-то и появляется 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
ищутся, и мы этого не хотим. В нашем Lispе мы хотим сделать:
(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
quote
Намного лучше! Кстати, этот шаблон настолько распространен в (в основном) макросах, что для этого есть специальный синтаксис. Обратная цитата:
(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
Сравните с (на Steel-Bank 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
) структур данных!
Если вы хотите узнать больше, я рекомендую книгу Peter Seibel Practical Common Lisp для практического подхода к изучению Lisp, если вы уже занимаетесь программированием в целом. Со временем в вашем путешествии по Lispу вы тоже начнете использовать пакеты. Ron Garret Руководство для идиотов по пакетам Common Lisp даст вам хорошее объяснение.
Счастливого взлома!
В нем говорится: "Не оценивай меня". Например, если вы хотите использовать список как данные, а не как код, вы должны поставить перед ним цитату. Например,
(print '(+ 3 4))
печатает "(+ 3 4)", тогда как
(print (+ 3 4))
печатает "7"
Другие люди ответили на этот вопрос замечательно, и Маттиас Бенкард выдает отличное предупреждение.
НЕ ИСПОЛЬЗУЙТЕ ЦИТИРОВАТЬ, ЧТОБЫ СОЗДАТЬ ЛИСТЫ, КОТОРЫЕ ВЫ ПОСЛЕДНЕЕ ИЗМЕНЯЕТЕ. Спецификация позволяет компилятору обрабатывать цитируемые списки как константы. Часто компилятор оптимизирует константы, создавая для них одно значение в памяти, а затем ссылаясь на это единственное значение из всех мест, где появляется константа. Другими словами, он может рассматривать константу как анонимную глобальную переменную.
Это может вызвать очевидные проблемы. Если вы изменяете константу, она может очень хорошо изменить другие применения одной и той же константы в полностью несвязанном коде. Например, вы можете сравнить некоторую переменную с '(1 1) в некоторой функции, и в совершенно другой функции запустите список с помощью' (1 1), а затем добавьте к нему больше вещей. После запуска этих функций вы можете обнаружить, что первая функция больше не соответствует вещам, потому что теперь она пытается сравнить переменную с "(1 1 2 3 5 8 13), что и возвращала вторая функция. Эти две функции полностью не связаны друг с другом, но они влияют друг на друга из-за использования констант. Даже более сумасшедшие плохие эффекты могут произойти, как и абсолютно нормальная итерация списка, неожиданно бесконечная петля.
Используйте цитату, когда вам нужен постоянный список, например, для сравнения. Используйте список, когда вы будете изменять результат.
Один ответ на этот вопрос гласит, что QUOTE "создает структуры данных списка". Это не совсем правильно. QUOTE является более фундаментальным, чем это. На самом деле, QUOTE - это тривиальный оператор: его цель - предотвратить что-либо вообще. В частности, он ничего не создает.
Что (QUOTE X) говорит в основном "ничего не делайте, просто дай мне X". X не обязательно должен быть списком (QUOTE (A B C)) или символом (QUOTE FOO). Это может быть любой объект. В самом деле, результат оценки списка, созданного (LIST "QUOTE SOME-OBJECT" ), всегда будет возвращать НЕКОТОРЫЙ ОБЪЕКТ, какой бы он ни был.
Теперь причина, по которой (QUOTE (A B C)) выглядит так, как будто она создала список, элементами которого являются A, B и C, является то, что такой список действительно является тем, что он возвращает; но в то время, когда оценивается форма QUOTE, список уже существует уже некоторое время (как компонент формы QUOTE!), созданный либо загрузчиком, либо читателем перед выполнением кода.
Одно из последствий этого, которое имеет тенденцию слишком часто поднимать новичков, заключается в том, что очень неразумно изменять список, возвращаемый формой QUOTE. Данные, возвращаемые QUOTE, для всех целей и целей рассматриваются как часть выполняемого кода и поэтому должны рассматриваться как доступные для чтения!
Котировка предотвращает выполнение или оценку формы, превращая ее в данные. В общем, вы можете выполнить данные, а затем eval'ing его.
quote создает структуры данных списка, например, следующие эквиваленты:
(quote a)
'a
Его также можно использовать для создания списков (или деревьев):
(quote (1 2 3))
'(1 2 3)
Вам, вероятно, лучше всего получить вводную книгу на lisp, например Практический общий Lisp (который доступен для чтения он-лайн).
Когда мы хотим передать сам аргумент, а не передавать значение аргумента, мы используем цитату. Это в основном связано с процедурой, проходящей во время использования списков, пар и атомов которые недоступны на языке программирования C (большинство людей начинают программировать с использованием программирования C, поэтому мы сбиваемся с толку) Это код на языке программирования Схемы, который является диалектом lisp, и я думаю, вы можете понять этот код.
(define atom? ; defining a procedure atom?
(lambda (x) ; which as one argument x
(and (not (null? x)) (not(pair? x) )))) ; checks if the argument is atom or not
(atom? '(a b c)) ; since it is a list it is false #f
Последняя строка (atom? 'abc) передает abc, как и процедуру, чтобы проверить, является ли abc атомом или нет, но когда вы проходите (atom? abc), тогда он проверяет значение abc и passses значение для него. Поскольку мы не предоставили ему никакой ценности
В Emacs Lisp:
Что можно указать?
Списки и символы.
Цитирование числа оценивается самим номером:
'5
совпадает с 5
.
Что происходит при цитировании списков?
Например:
'(one two)
оценивается как
(list 'one 'two)
, который оценивается как
(list (intern "one") (intern ("two")))
.
(intern "one")
создает символ с именем "один" и сохраняет его в "центральной" хеш-карте, поэтому в любое время, когда вы говорите 'one
, тогда символ с именем "one"
будет просматриваться на этой центральной хэш-карте.
Но что такое символ?
Например, в OO-языках (Java/Javascript/Python) символ может быть представлен как объект, который имеет поле name
, которое представляет собой имя символа, подобное "one"
выше, а также данные и/или код может быть связан с этим объектом.
Таким образом, символ в Python может быть реализован как:
class Symbol:
def __init__(self,name,code,value):
self.name=name
self.code=code
self.value=value
В Emacs Lisp например, символ может иметь 1) данные, связанные с ним AND (в то же время - для одного и того же символа) 2) связанный с ним код - в зависимости от контекста, либо данные, либо код получает вызов.
Например, в Elisp:
(progn
(fset 'add '+ )
(set 'add 2)
(add add add)
)
имеет значение 4
.
Потому что (add add add)
оценивается как:
(add add add)
(+ add add)
(+ 2 add)
(+ 2 2)
4
Итак, например, используя класс Symbol
, который мы определили в Python выше, этот add
ELisp-Symbol может быть написан на Python как Symbol("add",(lambda x,y: x+y),2)
.
Большое спасибо людям за IRС#emacs за объяснение символов и цитат.
Цитата возвращает внутреннее представление своих аргументов. После вспашки слишком много объяснений того, что цитата не делает, когда лампочка продолжалась. Если REPL не преобразовал имена функций в UPPER-CASE, когда я их процитировал, это могло бы не рассердиться на меня.
Итак. Обычные функции Lisp преобразуют свои аргументы во внутреннее представление, оценивают аргументы и применяют эту функцию. Цитата преобразует свои аргументы во внутреннее представление и просто возвращает это. Технически это правильно сказать, что цитата говорит: "Не оценивайте", но когда я пытался понять, что он сделал, говорить мне, что это не делает, было неприятно. Мой тостер не оценивает функции Lisp; но это не то, как вы объясните, что делает тостер.
Короткий ответ Anoter:
quote
означает без его оценки, а backquote - это цитата, но оставляйте задние двери.
Хорошая референция:
Справочное руководство Emacs Lisp сделало его очень понятным
9.3 Цитирование
Копия специальной формы возвращает свой единственный аргумент, как написано, без его оценки. Это обеспечивает возможность включения в программу постоянных символов и списков, которые не являются самооценками. (Нет необходимости цитировать самооценки объектов, таких как числа, строки и векторы.)
Специальная форма: объект цитаты
This special form returns object, without evaluating it.
Потому что цитата используется так часто в программах, Lisp предоставляет удобный для нее синтаксис чтения. Апострофный символ (''), за которым следует объект Lisp (в синтаксисе чтения), расширяется до списка, первым элементом которого является цитата, а вторым элементом является объект. Таким образом, синтаксис чтения "x" является аббревиатурой для (quote x).
Вот несколько примеров выражений, которые используют цитату:
(quote (+ 1 2))
⇒ (+ 1 2)
(quote foo)
⇒ foo
'foo
⇒ foo
''foo
⇒ (quote foo)
'(quote foo)
⇒ (quote foo)
9.4 Backquote
Конструкции Backquote позволяют процитировать список, но выборочно оценивать элементы этого списка. В простейшем случае он идентичен цитате специальной формы (описанной в предыдущем разделе, см. "Котировка" ). Например, эти две формы дают одинаковые результаты:
`(a list of (+ 2 3) elements)
⇒ (a list of (+ 2 3) elements)
'(a list of (+ 2 3) elements)
⇒ (a list of (+ 2 3) elements)
Специальный маркер ', внутри аргумента backquote указывает значение, которое не является постоянным. Оценщик Emacs Lisp оценивает аргумент ', и помещает значение в структуру списка:
`(a list of ,(+ 2 3) elements)
⇒ (a list of 5 elements)
Подстановка с ', разрешена также на более глубоких уровнях структуры списка. Например:
`(1 2 (3 ,(+ 4 5)))
⇒ (1 2 (3 9))
Вы также можете объединить оценочное значение в результирующий список, используя специальный маркер ', @. Элементы сплайсированного списка становятся элементами на том же уровне, что и другие элементы результирующего списка. Эквивалентный код без использования `` часто нечитабелен. Вот несколько примеров:
(setq some-list '(2 3))
⇒ (2 3)
(cons 1 (append some-list '(4) some-list))
⇒ (1 2 3 4 2 3)
`(1 ,@some-list 4 ,@some-list)
⇒ (1 2 3 4 2 3)
Code is data and data is code. There is no clear distinction between them.
Это классическое утверждение, которое знает любой программист на lisp.
Когда вы указываете код, этот код будет данными.
1 ]=> '(+ 2 3 4)
;Value: (+ 2 3 4)
1 ]=> (+ 2 3 4)
;Value: 9
Когда вы цитируете код, результатом будут данные, которые представляют этот код. Поэтому, когда вы хотите работать с данными, представляющими программу, вы указываете эту программу. Это также верно для атомарных выражений, а не только для списков:
1 ]=> 'code
;Value: code
1 ]=> '10
;Value: 10
1 ]=> '"ok"
;Value: "ok"
1 ]=> code
;Unbound variable: code
Предположим, вы хотите создать язык программирования, встроенный в lisp - вы будете работать с программами, которые указаны в схеме (например, '(+ 2 3)
) и которые интерпретируются как код на языке, который вы создаете, предоставляя программам семантическую интерпретацию, В этом случае вам нужно использовать цитату для хранения данных, в противном случае они будут оцениваться на внешнем языке.