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

Объяснить 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)

У меня есть подозрение, что у меня есть фатальная ошибка где-то в фундаментальном понимании того, как здесь работают символы. Что не так с приведенным выше кодом?

4b9b3361

Ответ 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 (который неявно разыменован), уступая его связанному объекту