Когда использовать '(или цитату) в Лиспе?

114

Пройдя через основные части вводной книги по Лиспу, я все еще не мог понять, что делает специальный оператор (quote)(или его эквивалент '), но это касалось всего кода Лиспа, который я видел.

Что оно делает?

Кристиан Ромо
источник

Ответы:

178

Краткий ответ Обойдите правила оценки по умолчанию и не оценивайте выражение (символ или s-exp), передавая его функции точно так, как оно было введено.

Длинный ответ: правило оценки по умолчанию

Когда вызывается обычная (я вернусь к этому позже) функция, все переданные ей аргументы оцениваются. Это означает, что вы можете написать это:

(* (+ a 2)
   3)

Что, в свою очередь, оценивает (+ a 2), вычисляя aи 2. Значение символа aищется в текущем наборе привязок переменных, а затем заменяется. Say 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)))

Это похоже на using quote, но с возможностью явно оценить некоторые аргументы, добавив к ним запятую. Результат эквивалентен использованию list, но если вы генерируете код из макроса, вам часто нужно оценивать только небольшие части возвращаемого кода, поэтому обратная кавычка больше подходит. Для более коротких списков listможет быть более читабельным.

Эй, ты забыл quote!

Итак, что же нам остается? Ах да, что на quoteсамом деле делает? Он просто возвращает свой аргумент (ы) без оценки! Помните, что я говорил в начале о регулярных функциях? Оказывается, что некоторые операторы / функции должны не оценивать свои аргументы. Например, IF - вы бы не хотели, чтобы ветвь else оценивалась, если она не была взята, верно? Так называемые специальные операторы вместе с макросами работают так. Специальные операторы также являются «аксиомой» языка - минимальным набором правил - на основе которых вы можете реализовать остальную часть Лиспа, комбинируя их вместе различными способами.

Но вернемся к 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 ) структуры данных!

Если вы хотите узнать больше, я рекомендую книгу Питера Зейбела « Практический Common Lisp» для практического подхода к изучению Lisp, если вы уже занимаетесь программированием в целом. В конце концов, на пути к Lisp вы тоже начнете использовать пакеты. Руководство Рона Гаррета « Идиот» по пакетам Common Lisp даст вам хорошее объяснение этого.

Удачного взлома!

Микаэль Янссон
источник
В моем emacs SBCL настроен, и когда я набираю 'this' is 'true', он возвращает только последнее, т.е. TRUE на выходе. Даже в портакле я получаю такой же результат
Тоторо
@Totoro Возвращаемое значение функции или просто нескольких операторов в lisp является последним выражением, поэтому оно действительно возвращает this, тогда is, тогда true, но вы видите только последнее возвращенное выражение . (это и правда - отдельные утверждения)
Wezl
52

Он говорит: «Не оценивайте меня». Например, если вы хотите использовать список как данные, а не как код, вы должны поставить перед ним цитату. Например,

(print '(+ 3 4))печатает «(+ 3 4)», а (print (+ 3 4))печатает «7»

Адам Розенфилд
источник
Как можно тогда его оценить, например, есть ли unquoteкоманда?
Лайм
3
@William лепечет имеет удобную функцию под названием eval: (print (eval '(+ 3 4))). Вот что делает Lisp таким замечательным: списки - это код, а код - это списки, поэтому программа на Лиспе может манипулировать собой.
darkfeline
18

Другие люди превосходно ответили на этот вопрос, и Матиас Бенкард делает отличное предупреждение.

НЕ ИСПОЛЬЗУЙТЕ ЦИТАТУ ДЛЯ СОЗДАНИЯ СПИСКОВ, КОТОРЫЕ ВЫ ПОЗЖАЕТЕ ИЗМЕНИТЕ. Спецификация позволяет компилятору обрабатывать цитируемые списки как константы. Часто компилятор оптимизирует константы, создавая для них одно значение в памяти и затем ссылаясь на это единственное значение из всех мест, где появляется константа. Другими словами, он может обрабатывать константу как анонимную глобальную переменную.

Это может вызвать очевидные проблемы. Если вы измените константу, она вполне может изменить другие варианты использования той же константы в совершенно несвязанном коде. Например, вы можете сравнить некоторую переменную с '(1 1) в какой-либо функции, а в совершенно другой функции начать список с' (1 1), а затем добавить в него что-то еще. После запуска этих функций вы можете обнаружить, что первая функция больше не соответствует вещам должным образом, потому что теперь она пытается сравнить переменную с '(1 1 2 3 5 8 13), что и вернула вторая функция. Эти две функции совершенно не связаны, но они влияют друг на друга из-за использования констант. Могут произойти даже более безумные плохие эффекты, такие как совершенно нормальная итерация списка, внезапно бесконечный цикл.

Используйте цитату, когда вам нужен постоянный список, например, для сравнения. Используйте список, когда будете изменять результат.

Ксантир
источник
Похоже, вам следует использовать (list (+ 1 2)) большую часть времени. Если да, то как предотвратить оценку (+ 1 2)внутри такого примера? Есть ли unquoteкоманда?
Лайм
1
Вы хотите эквивалент '((3))или эквивалент '((+ 1 2))? В последнем случае, вы должны использовать более list: (list (list '+ 1 2)). Или, если вам нужен эквивалент '(+ 1 2), просто (list '+ 1 2). И помните, если вы не изменяете список, не стесняйтесь использовать цитату: ничего плохого в том, '(+ 1 2)если вы просто сравниваете с ним или что-то в этом роде.
Ксантир 08
1
Не возражаете ли вы указать, где цитируемые списки должны рассматриваться как константы?
Лайм
HyperSpec clhs.lisp.se/Body/s_quote.htm говорит, что поведение не определено, если цитируемый объект деструктивно изменен. Подразумевается, что это позволяет подразумеваемым объектам обрабатывать значения как атомарные.
Ксантир
14

Один из ответов на этот вопрос гласит, что QUOTE «создает структуры данных списка». Это не совсем так. QUOTE более фундаментален, чем это. Фактически, QUOTE - тривиальный оператор: его цель - предотвратить что-либо вообще. В частности, он ничего не создает.

В (ЦИТАТА X) говорится: «Ничего не делайте, просто дайте мне X». X не обязательно должен быть списком, как в (QUOTE (ABC)), или символом, как в (QUOTE FOO). Это может быть любой объект. Действительно, результат оценки списка, созданного (LIST 'QUOTE SOME-OBJECT), всегда будет просто возвращать SOME-OBJECT, каким бы он ни был.

Причина, по которой (QUOTE (ABC)) кажется, будто он создал список, элементы которого - A, B и C, заключается в том, что такой список действительно является тем, что он возвращает; но на момент оценки формы QUOTE список, как правило, уже существует некоторое время (как компонент формы QUOTE!), созданный либо загрузчиком, либо читателем перед выполнением кода.

Одно из следствий этого, которое довольно часто сбивает с толку новичков, заключается в том, что очень неразумно изменять список, возвращаемый формой QUOTE. Данные, возвращаемые QUOTE, для всех намерений и целей должны рассматриваться как часть выполняемого кода и, следовательно, должны рассматриваться как доступные только для чтения!

Матиас Бенкард
источник
11

Цитата предотвращает выполнение или оценку формы, вместо этого превращая ее в данные. В общем, вы можете выполнить данные, затем оценив их.

quote создает структуры данных списка, например, следующие эквивалентны:

(quote a)
'a

Его также можно использовать для создания списков (или деревьев):

(quote (1 2 3))
'(1 2 3)

Вам, вероятно, лучше всего получить вводную книгу по лиспу, например, Practical Common Lisp (которую можно прочитать в Интернете).

Кайл Бертон
источник
3

В Emacs Lisp:

Что можно цитировать?

Списки и символы.

Цитирование числа оценивает само число: '5то же самое, что5 .

Что происходит, когда вы цитируете списки?

Например:

'(one two) оценивает

(list 'one 'two) что оценивается как

(list (intern "one") (intern ("two"))).

(intern "one")создает символ с именем "один" и сохраняет его в "центральной" хэш-карте, поэтому в любое время, когда вы произносите это слово, 'oneсимвол с именем"one" будет найден в этой центральной хэш-карте.

Но что такое символ?

Например, в объектно-ориентированных языках (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) связанные с ним данные И (одновременно - для того же символа) 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 выше, этот addELisp-Symbol может быть записан на Python какSymbol("add",(lambda x,y: x+y),2) .

Большое спасибо людям из IRC #emacs за то, что они объяснили мне символы и цитаты.

jhegedus
источник
2

Когда мы хотим передать сам аргумент вместо передачи значения аргумента, мы используем кавычки. Это в основном связано с процедурой, проходящей во время использования списков, пар и атомов, которые недоступны в языке программирования C (большинство людей начинают программировать, используя программирование на C, поэтому мы запутываемся) .Это код на языке программирования Scheme, который является диалектом 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 и передает значение в Это. Поскольку мы не придали ему никакой ценности

неизвестная ошибка
источник
2
Code is data and data is code.  There is no clear distinction between them.

Это классическое утверждение, известное любому лисп-программисту.

Когда вы цитируете код, этот код будет данными.

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)) и которые интерпретируются как код на языке, который вы создаете, предоставляя программам семантическую интерпретацию. В этом случае вам необходимо использовать кавычки для хранения данных, иначе они будут оцениваться на внешнем языке.

alinsoar
источник
1

Quote возвращает внутреннее представление своих аргументов. После того, как мы проработали слишком много объяснений того, чего не хватает цитатой , вот когда загорелась лампочка. Если бы REPL не преобразовывал имена функций в ЗАПИСИ, когда я их цитировал, меня, возможно, не осенило.

Так. Обычные функции Lisp преобразуют свои аргументы во внутреннее представление, оценивают аргументы и применяют функцию. Quote преобразует свои аргументы во внутреннее представление и просто возвращает его. Технически правильно сказать, что цитата гласит: «Не оценивайте», но когда я пытался понять, что она делает, рассказывать мне, чего она не делает, было разочарованием. Мой тостер также не оценивает функции Lisp; но не так можно объяснить, что делает тостер.

Стив
источник
1

Другой короткий ответ:

quoteозначает без оценки, а обратная кавычка - это цитата, но оставить лазейки .

Хорошая ссылка:

Справочное руководство Emacs Lisp очень ясно

9.3 Цитирование

Цитата специальной формы возвращает свой единственный аргумент в том виде, в котором он написан, без его оценки. Это дает возможность включать в программу постоянные символы и списки, которые не являются объектами самооценки. (Необязательно указывать самооценочные объекты, такие как числа, строки и векторы.)

Специальная форма: объект цитаты

This special form returns object, without evaluating it. 

Поскольку кавычки так часто используются в программах, Lisp предоставляет для них удобный синтаксис чтения. Символ апострофа ('' '), за которым следует объект Lisp (в синтаксисе чтения), расширяется до списка, первым элементом которого является кавычка, а вторым элементом - объект. Таким образом, синтаксис чтения 'x является сокращением для (кавычки x).

Вот несколько примеров выражений, в которых используется кавычка:

(quote (+ 1 2))
      (+ 1 2)

(quote foo)
      foo

'foo
      foo

''foo
      (quote foo)

'(quote foo)
      (quote foo)

9.4 Обратная кавычка

Конструкции обратных кавычек позволяют цитировать список, но выборочно оценивать элементы этого списка. В простейшем случае он идентичен цитате специальной формы (описанной в предыдущем разделе; см. Цитирование). Например, эти две формы дают одинаковые результаты:

`(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

'(a list of (+ 2 3) elements)
      (a list of (+ 2 3) elements)

Специальный маркер ',' внутри аргумента обратной кавычки указывает значение, которое не является постоянным. Оценщик 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)
Andrew_1510
источник