Подтвердить что ты не робот

Какая разница между '() и (списком) в Clojure?

Раздел API Cheatsheet в списках, похоже, указывает, что '() - это конструктор списка, как и (list), но я обнаружили, что на практике они не совсем то же самое. Например, данный:

(def foo "a")
(def bar "b")
(def zip "c")

Следующее утверждение:

(apply str '(foo bar zip))

выводит вывод "foobarzip", чего я не ожидал.

Но предположительно эквивалентный:

(apply str (list foo bar zip))

создает "abc", как я ожидал.

Что здесь происходит? Если есть "стенография" для списка в Clojure (например, {} для карт и [] для векторов), что это такое?

4b9b3361

Ответ 1

В lisps ' (например, quote) цитирует его аргументы, т.е. сохраняет их в значительной степени точно так же, как записано в их форме s-exp, в том числе не оценивая ничего внутри.

Другими словами, '(foo bar zip) создает список, содержащий символы foo, bar, zip; а (list foo bar zip) создает список, содержащий значения foo, bar, zip. В первом случае str будет преобразовывать сами символы в строки и затем конкатенировать их.

В качестве демонстрации этого:

=> (def foo "a")
=> (type (first '(foo)))
clojure.lang.Symbol
=> (type (first (list foo))) 
java.lang.String

Ответ 2

Разница в том, что, как вы видите, цитируется литеральный синтаксис '(). Это означает, что символы внутри не оцениваются. Чтобы использовать литералы списка при оценке элементов, вы можете использовать макрос читателя syntax-quote:

user=> (apply str `(~foo ~bar ~zip))
"abc"

Ответ 3

Когда вы пишете:

(def foo "a")
(def bar "b")
(def zip "c")

Вы определили три символа: foo, bar и zip, связанные со значениями: "a", "b" и "c".

Связь хранится внутри таблицы namsepace, поэтому, если использовать REPL, пространство имен будет user по умолчанию, поэтому теперь таблица пространства имен пользователя будет содержать:

{foo
#'user/foo
bar
#'user/bar,
zip
#'user/zip}

Вы можете увидеть таблицу пространства имен, выполнив: (ns-interns *ns*)

Теперь, если вы пишете: (foo bar zip) внутри Clojure, будет четыре способа, которые читатель может прочитать. Вам нужно будет указать читателю, как его читать. То, что `, ' и list вступают в игру.

Случай без индикатора:

(foo bar zip)

Когда он просто написан без какого-либо индикатора, читатель интерпретирует это как S-выражение и будет интерпретировать foo как отображение символа функции, причем bar и zip в качестве символов, отображающих значения, подлежащие передаче в функцию foo.

В нашем случае это вызовет исключение:

java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn

Это связано с тем, что не определена функция foo, foo была связана с "a", которая является строкой, а не функцией IFn (a Clojure).

Если была определена функция foo, она вызывала бы foo в качестве аргумента "b" и "c".

Случай list:

(list foo bar zip)

При использовании символа списка читатель фактически интерпретирует это так же, как и случай индикатора no. То есть он ищет символ list, который сопоставляется с функцией, которая будет принимать связанные значения, сопоставленные с foo, bar и zip в качестве аргументов. Функция list предварительно определена Clojure внутри пространства имен clojure.core; он возвращает список его аргументов.

Поэтому, когда читатель ищет list, он находит функцию clojure.core, затем он ищет символ foo и находит, что он сопоставляется с "a" и т.д. После того, как он нашел все сопоставление для символов, он вызывает list и передает ему связанные значения foo bar zip, которые были бы "a" "b" "c". Итак, (list foo bar zip) совпадает с (list "a" "b" "c").

Случай ':

'(foo bar zip)

Цитата ' говорит читателю, что следующая форма должна читаться как есть. В нашем случае foo, bar и zip являются символами, а (foo bar zip) - это список символов. Поэтому читатель интерпретирует это как список символов.

Вот почему при запуске (apply str '(foo bar zip)) он будет вызывать str 'foo 'bar 'zip, который даст вам foobarzip. То есть, он преобразует каждый символ в список символов в представление String, а затем объединит их в одну строку.

Принимая форму как есть, она передает в качестве аргумента список символов без оценки символов, то есть без поиска того, с чем они связаны. Если вы запустили (eval '(foo bar zip)), вы передали бы список символов в eval, а eval оценили символы в значениях и вернули список значений, на которые были сопоставлены символы. Итак, вы можете придумать цитату ' как передать код в виде кода.

Случай `:

`(foo bar zip)

Это немного сложнее. Читатель увидит обратный отсчет ` и рекурсивно разрешит символы внутри списка символов, чтобы получить список полностью квалифицированных символов.

В принципе, когда читатель просматривает символы внутри таблицы символов, он делает это из таблицы символов текущего пространства имен. Полностью квалифицированный символ - это символ, который включает информацию о пространстве имен. Поэтому при запуске `(foo bar zip) читатель заменит эти символы на полностью квалифицированные, превратив его в (user.foo user.bar user.zip).

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

`(foo ~bar zip)

предоставит вам

(clojure.foo "b" clojure.zip)

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