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

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

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

9 ответов

Решение

Краткий ответ Обходите правила оценки по умолчанию и не оценивайте выражение (символ или 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

Сравните с (на 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, если вы уже занимаетесь программированием в целом. Со временем в вашем путешествии по Лиспу вы тоже начнете использовать пакеты. Руководство Идиота по пакетам 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 X) говорит, в основном "ничего не делай, просто дай мне X". X не обязательно должен быть списком, как в (QUOTE (A B C)), или символом, как в (QUOTE FOO). Это может быть любой объект. Действительно, результат оценки списка, который создается (LIST 'QUOTE SOME-OBJECT) всегда будет просто возвращать SOME-OBJECT, каким бы он ни был.

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

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

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

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

(quote a)
'a

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

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

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

В Emacs Lisp:

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

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

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

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

Например:

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

(list 'one 'two) который оценивает

(list (intern "one") (intern ("two"))),

(intern "one") создает символ с именем "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) код, связанный с ним - в зависимости от контекста вызываются либо данные, либо код.

Например, в Элиспе:

(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),

Большое спасибо ребятам из IRC #emacs за объяснение символов и цитат.

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

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

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

Короткий ответ:

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)

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

(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 и передает значение в Это. С тех пор мы не предоставили ему никакой ценности

Другие вопросы по тегам