Объяснить Clojure Символы
У меня есть символ "a"
, связанный с функцией:
(defn a []
(println "Hello, World"))
user=> a
#<user$a__292 [email protected]>
user=> (a)
Hello, World
nil
Затем я использую синтаксическую цитату, она "разрешает символ в текущем контексте, давая полностью квалифицированный символ", согласно Clojure documentation. Но почему я не могу использовать его так же, как неквалифицированный символ?
user=> `a
user/a
user=> (`a)
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)
Второй вопрос: если у меня есть символ в списке, почему я не могу его оценить так же, как если бы я сам оценил символ?
user=> (def l '(a 1 2))
#'user/l
user=> 'l
l
user=> (first l)
a
user=> ((first l))
java.lang.IllegalArgumentException: Wrong number of args passed to: Symbol (NO_SOURCE_FILE:0)
У меня есть подозрение, что у меня есть фатальная ошибка где-то в фундаментальном понимании того, как здесь работают символы. Что не так с приведенным выше кодом?
Ответы
Ответ 1
REPL = прочитать цикл печати eval. Шаг через процесс чтения-eval.
READ: Clojure видит строку "(`a)"
, анализирует ее и заканчивает структуру данных. Во время чтения макросы читателя расширяются, и больше ничего не происходит. В этом случае читатель расширяет обратную кавычку и заканчивается этим:
user> (read-string "(`a)")
((quote user/a))
EVAL: Clojure пытается оценить этот объект. Правила оценки варьируются в зависимости от того, на какой объект вы смотрите.
- Некоторые объекты оценивают как себя (числа, строки, ключевые слова и т.д.).
- Символ оценивается путем разрешения его в каком-либо пространстве имен для получения некоторого значения (обычно).
- Список оценивается с помощью макрорасширения списка до тех пор, пока не останется макросов слева, а затем рекурсивно оценит первый элемент в списке, чтобы получить некоторое результирующее значение, затем используя значение первого элемента в списке, чтобы решить, что делать. Если первое значение является специальной формой, происходит особый материал. В противном случае первое значение рассматривается как функция и вызывается со значениями остальной части списка (полученных рекурсивной оценкой всех элементов списка) в качестве параметров.
- и др.
Обратитесь к clojure.lang.Compiler/analyzeSeq
в источнике Clojure, чтобы увидеть правила оценки для списков или clojure.lang.Compiler/analyzeSymbol
для символов. Существует множество других правил оценки.
Пример
Предположим, вы это сделали:
user> (user/a)
REPL завершает это внутри:
user> (eval '(user/a))
Clojure видит, что вы оцениваете список, поэтому он оценивает все элементы в списке. Первый (и только) элемент:
user> (eval 'user/a)
#<user$a__1811 [email protected]>
a
не является специальной формой, и этот список не обязательно должен быть макрорасширен, поэтому символ a
просматривается в пространстве имен user
и результирующее значение здесь a fn
. Так вызывается fn
.
Ваш код
Но вместо этого у вас есть это:
user> (eval '((quote user/a)))
Clojure оценивает первый элемент в списке, который сам является списком.
user> (eval '(quote user/a))
user/a
Он оценил первый элемент в этом под-списке quote
, который является специальной формой, поэтому применяются специальные правила, и он возвращает свой аргумент (Symbol a
) без оценки.
Символ a
является значением в этом случае, поскольку значение fn
было выше. Таким образом, Clojure рассматривает сам символ как функцию и вызывает его. В Clojure все, что реализует интерфейс Ifn
, может быть вызвано как fn
. Так получилось, что clojure.lang.Symbol
реализует Ifn
. Символ, называемый функцией, ожидает один параметр, коллекцию, и он выглядит в этой коллекции. Он должен был использоваться следующим образом:
user> ('a {'a :foo})
:foo
Это то, что он пытается сделать здесь. Но вы не передаете никаких параметров, поэтому вы получаете сообщение об ошибке "Неверное количество аргументов, переданных в: Symbol" (ожидается коллекция).
Для вашего кода для работы вам понадобятся два уровня eval
. Это работает, надеюсь, вы можете понять, почему:
user> (eval '((eval (quote user/a))))
Hello, world
user> ((eval (first l)))
Hello, world
Обратите внимание, что в реальном коде использование eval
напрямую - это действительно плохая идея. Макросы - лучшая идея. Я использую его только для демонстрации.
Посмотрите Compiler.java
в источнике Clojure, чтобы увидеть, как все это происходит. Это не слишком сложно.
Ответ 2
Использование символа как функции - это не то же самое, что его оценка. Символы как функции работают так же, как ключевые слова. Вот так:
user=> (declare a)
#'user/a
user=> (def a-map {'a "value"})
#'user/a-map
user=> ('a a-map)
"value"
user=>
Это не то, как вы обычно используете символ. Они чаще используются для поиска vars в пространстве имен и при генерации кода в макросе.
Чтобы сломать слои косвенности, определите "x" как 1 и посмотрите, что произойдет:
user=> (def x 1)
#'user/x
Используя def
, мы создали "var". Имя var является символом user/x. Специальная форма def
возвращает var непосредственно в repl, и это то, что мы можем видеть напечатанным. Попробуйте и возьмите этот var:
user=> #'x
#'user/x
Синтаксис #'
- это макрос читателя, который говорит: "Дайте мне var, на который ссылается следующий символ". И в нашем случае этот символ "х". Мы получили тот же var, что и раньше. Vars являются указателями на значения и могут быть разыменованы:
user=> (deref #'x)
1
Но var нужно найти, прежде чем он может быть разыменован. Именно здесь вступает в действие вызывающая способность символов. Пространство имен похоже на карту, где символы являются ключами, а vars - значениями, и когда мы явно называем символ, мы неявно просматриваем его var в нашем пространстве имен. Вот так:
user=> ('x (.getMappings *ns*))
#'user/x
Хотя, в действительности, это, вероятно, больше похоже на это:
user=> (.findInternedVar *ns* 'x)
#'user/x
И теперь мы прошли полный круг в пути символа без кавычек:
user=> (deref (.findInternedVar *ns* 'x))
1
user=> x
1
Оба не совсем равны. Поскольку оценщик делает это для всех символов, включая deref
и * ns *.
Дело в цитировании заключается в том, что вы по сути обходите весь этот механизм и просто возвращаете простой символ. Как и макрос читателя #'
, вы получите простую vars обратно, макросы "и" читателя получат простые символы обратно с квалификацией пространства имен или без них соответственно:
user=> 'x
x
user=> `x
user/x
Ответ 3
user = > (def l '(a 1 2)) user = > ((первый l))
Включите это:
user = > (def l `(~ a 1 2))
Здесь здесь разрешается символ a в соответствии с его соответствующим var, а обратная сторона делает работу без кавычек.
В общем, вы должны понимать разницу между vars (которые связаны с чем-то) и символами (которые никогда не связаны ни с чем).
Я попытаюсь объяснить это (в надежде, что моя экспланация не смущает вас дальше):
user=> (def v "content")
#'user/content
- > определяет переменную var в текущем пространстве имен под символом 'v (полностью квалифицированный пользователь /v, предполагая, что это текущее пространство имен) и связывает его (var, а не символ) с объектом "content".
user=> v
"content"
- > разрешает v в var и получает связанное значение
user=> #'v
#'user/v
- > разрешает самому var
user=> 'v
v
- > ничего не разрешает, просто простой символ (к сожалению, REPL не указывает это, печатая 'v as v)
user=> `v
user/v
- > , как вы уже цитировали, разрешает символ в текущем контексте (пространство имен), но результат по-прежнему является символом (полностью квалифицированным), а не пользователем var/v
user=> '(v)
(v)
- > обычное цитирование, ничего не разрешает
user=> `(v)
(user/v)
- > цитата синтаксиса, то же самое, что и цитирование, но разрешает символы для символов, обозначенных пространством имен
user=> `(~v)
("content")
- > разрешить символ для своего var (который неявно разыменован), уступая его связанному объекту