В чем разница между цитатой и списком?

Я знаю, что вы можете использовать ' (ака quote), чтобы создать список, и я использую это все время, вот так:

> (car '(1 2 3))
1

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

> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
  expected a procedure that can be applied to arguments
  given: '+

Когда я использую list, оно работает:

> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)

Зачем? я думал ' было просто удобное сокращение, так почему поведение отличается?

1 ответ

Решение

TL;DR: они разные; использование list в случае сомнений.

Эмпирическое правило: использовать list всякий раз, когда вы хотите, чтобы аргументы оценивались; quote "Распределяет" по своим аргументам, так '(+ 1 2) как (list '+ '1 '2), Вы получите символ в списке, а не функцию.


Углубленный взгляд на list а также quote

В схеме и ракетке, quote а также list это совершенно разные вещи, но поскольку оба они могут быть использованы для создания списков, путаница является общей и понятной. Между ними есть невероятно важное различие: list простая старая функция, в то время как quote (даже без специального ' синтаксис) - это особая форма. То есть, list может быть реализована в простой схеме, но quote не может быть.

list функция

list Функция на самом деле намного проще, поэтому давайте начнем с нее. Это функция, которая принимает любое количество аргументов и собирает аргументы в список.

> (list 1 2 3)
(1 2 3)

Этот приведенный выше пример может сбить с толку, потому что результат печатается как quoteв состоянии s-выражение, и это правда, в этом случае два синтаксиса эквивалентны. Но если мы немного усложним, вы увидите, что все по-другому:

> (list 1 (+ 1 1) (+ 1 1 1))
(1 2 3)
> '(1 (+ 1 1) (+ 1 1 1))
(1 (+ 1 1) (+ 1 1 1))

Что происходит в quote пример? Ну, мы обсудим это через минуту, но сначала посмотрим на list, Это просто обычная функция, поэтому она следует стандартной семантике оценки Scheme: она оценивает каждый из своих аргументов, прежде чем они будут переданы в функцию. Это означает, что выражения как (+ 1 1) будет уменьшен до 2 прежде чем они будут собраны в список.

Это поведение также видно при предоставлении переменных для функции списка:

> (define x 42)
> (list x)
(42)
> '(x)
(x)

С list, x оценивается перед передачей в list, С quoteвсе сложнее.

Наконец, потому что list это просто функция, она может использоваться так же, как и любая другая функция, в том числе в более высоком порядке. Например, его можно передать map функция, и она будет работать соответствующим образом:

> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))

quote форма

Цитата, в отличие от listЭто особая часть Лиспса. quote форма является особенной, потому что она получает специальное сокращение читателя, ', но это также особенное даже без этого. В отличие от list, quote не является функцией, и поэтому ей не нужно вести себя как единое целое - у нее есть свои собственные правила.

Краткое обсуждение исходного кода Lisp

В Лиспе, производным которого являются Scheme и Racket, весь код фактически состоит из обычных структур данных. Например, рассмотрим следующее выражение:

(+ 1 2)

Это выражение на самом деле является списком, и оно имеет три элемента:

  • + условное обозначение
  • число 1
  • число 2

Все эти значения являются нормальными значениями, которые могут быть созданы программистом. Это действительно легко создать 1 значение, потому что оно оценивает себя: вы просто набираете 1, Но символы и списки сложнее: по умолчанию символ в исходном коде выполняет поиск переменной! То есть символы не являются самооценочными:

> 1
1
> a
a: undefined
  cannot reference undefined identifier

Однако, как оказалось, символы в основном являются строками, и на самом деле мы можем конвертировать между ними:

> (string->symbol "a")
a

Списки делают даже больше, чем символы, потому что по умолчанию список в исходном коде вызывает функцию! дела (+ 1 2) смотрит на первый элемент в списке, + Symbol, ищет функцию, связанную с ней, и вызывает ее с остальными элементами в списке.

Однако иногда вы можете отключить это "специальное" поведение. Вы можете просто получить список или получить символ без его оценки. Для этого вы можете использовать quote,

Смысл цитаты

Учитывая все это, вполне очевидно, что quote делает: он просто "отключает" специальное поведение оценки для выражения, которое он переносит. Например, рассмотрим quoteобозначение символа:

> (quote a)
a

Точно так же рассмотрим quoteсоставление списка:

> (quote (a b c))
(a b c)

Неважно, что вы даете quote, всегда будет, всегда выкладывай на тебя. Не больше, не меньше. Это означает, что если вы дадите ему список, ни одно из подвыражений не будет оценено - не ожидайте, что они будут! Если вам нужна оценка любого вида, используйте list,

Теперь можно спросить: что произойдет, если вы quote что-то кроме символа или списка? Ну, ответ... ничего! Вы просто получите это обратно.

> (quote 1)
1
> (quote "abcd")
"abcd"

Это имеет смысл, так как quote до сих пор просто выплевывает то, что вы даете. Вот почему "литералы", такие как числа и строки, иногда называются "само-цитированием" на языке Lisp.

Еще одна вещь: что произойдет, если вы quote выражение, содержащее quote? То есть, что, если вы "двойной quote"?

> (quote (quote 3))
'3

Что там произошло? Ну помни что ' на самом деле просто прямое сокращение для quoteтак что ничего особенного не произошло вообще! Фактически, если в вашей схеме есть способ отключить сокращения при печати, это будет выглядеть так:

> (quote (quote 3))
(quote 3)

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

> (car (quote (quote 3)))
quote

Если вы догадались 3, ты неправ. Помните, quote отключает все вычисления и выражение, содержащее quote символ все еще просто список. Поиграйте с этим в REPL, пока вы не освоитесь с этим.

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

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


Приложение А. Квазиквотация

Так что, если цитата полностью отключает оценку, для чего она нужна? Ну, кроме составления списков строк, символов или чисел, которые известны заранее, не так много. К счастью, концепция квазиквотирования предоставляет возможность вырваться из цитаты и вернуться к обычной оценке.

Основы очень просты: вместо использования quoteиспользовать quasiquote, Обычно это работает так же, как quote всячески:

> (quasiquote 3)
3
> (quasiquote x)
x
> (quasiquote ((a b) (c d)))
((a b) (c d))

Что делает quasiquote особенным является то, что распознает специальный символ, unquote, Где бы unquote появляется в списке, а затем заменяется произвольным выражением, которое оно содержит:

> (quasiquote (1 2 (+ 1 2)))
(1 2 (+ 1 2))
> (quasiquote (1 2 (unquote (+ 1 2))))
(1 2 3)

Это позволяет вам использовать quasiquote создавать шаблоны, которые имеют "дыры" для заполнения unquote, Это означает, что на самом деле можно включать значения переменных внутри списков в кавычках:

> (define x 42)
> (quasiquote (x is: (unquote x)))
(x is: 42)

Конечно, используя quasiquote а также unquote довольно многословно, поэтому они имеют свои собственные сокращения, так же, как ', В частности, quasiquote является ` (backtick) и unquote является , (Запятая). С этими аббревиатурами приведенный выше пример гораздо более приемлем.

> `(x is: ,x)
(x is: 42)

И последнее замечание: квазицитату можно реализовать в Racket, используя довольно волосатый макрос, и это так. Это распространяется на использования list, consи, конечно же, quote,


Приложение B: Реализация list а также quote в схеме

Внедрение list это супер просто из-за того, как работает синтаксис rest аргумента. Это все, что вам нужно:

(define (list . args)
  args)

Это оно!

По сравнению, quote намного сложнее - на самом деле, это невозможно! Казалось бы, это вполне осуществимо, поскольку идея отключения оценки во многом напоминает макросы. Все же наивная попытка показывает проблему:

(define fake-quote
  (syntax-rules ()
    ((_ arg) arg)))

Мы просто берем arg и выплюнуть обратно... но это не работает. Почему бы и нет? Что ж, результат нашего макроса будет оценен, так что все напрасно. Мы могли бы расшириться до чего-то вроде quote расширив до (list ...) и рекурсивно цитируем элементы, например так:

(define impostor-quote
  (syntax-rules ()
    ((_ (a . b)) (cons (impostor-quote a) (impostor-quote b)))
    ((_ (e ...)) (list (impostor-quote e) ...))
    ((_ x)       x)))

К сожалению, однако, без процедурных макросов мы не можем обрабатывать символы без quote, Мы могли бы стать ближе, используя syntax-caseно даже тогда мы будем только подражать quoteПоведение, а не копирование.


Приложение C: Соглашения о печати на ракетках

Пробуя примеры из этого ответа в Racket, вы можете обнаружить, что они не печатаются так, как можно было бы ожидать. Часто они могут печатать с ведущими ', например, в этом примере:

> (list 1 2 3)
'(1 2 3)

Это связано с тем, что Racket по умолчанию печатает результаты в виде выражений, когда это возможно. То есть вы должны иметь возможность ввести результат в REPL и получить то же значение обратно. Лично я нахожу это поведение хорошим, но оно может сбивать с толку при попытке понять цитату, поэтому, если вы хотите отключить его, позвоните (print-as-expression #f)или измените стиль печати на "написать" в языковом меню DrRacket.

Наблюдаемое вами поведение является следствием того, что Scheme не рассматривает символы как функции.

Выражение '(+ - * /)производит значение, которое представляет собой список символов. Это просто потому, что (+ - * /) - это список символов, и мы просто цитируем его, чтобы подавить вычисление, чтобы получить этот объект буквально как значение.

Выражение (list + - * /)производит список функций. Это потому, что это вызов функции. Символические выражения list, +, -, * и /оцениваются. Все они являются переменными, которые обозначают функции, и поэтому сводятся к этим функциям. В list Затем вызывается функция, которая возвращает список оставшихся четырех функций.

В ANSI Common Lisp вызов символов как функций работает:

[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)

Когда символ используется там, где ожидается функция, заменяется привязка функции верхнего уровня символов, если она есть, и все круто.

Если вы хотите использовать list для создания списка символов, как и '(+ - * /), вы должны процитировать их индивидуально, чтобы скрыть их оценку:

(list '+ '- '* '/)

вы увидите это, если вы map в этом случае он потерпит неудачу точно так же.

Отображаемое вам сообщение об ошибке вводит в заблуждение:

expected a procedure that can be applied to arguments
given: '+

Аппликатор не выдан (quote +) вообще, но +. Здесь происходит то, что символ + печатается в режиме "печать как выражение", который является особенностью Racket, что, я думаю, вы и используете.

В режиме "печать как выражение" объекты печатаются с использованием синтаксиса, который необходимо прочитать и оценить для создания аналогичного объекта. См. Вопрос Stackru " Почему интерпретатор Racket раньше записывает списки с апострофом?"

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