В чем разница между цитатой и списком?
Я знаю, что вы можете использовать '
(ака 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 раньше записывает списки с апострофом?"