В чем смысл символов в Clojure привязываться к базовому объекту и иметь необязательное отдельное значение? Возможно, что-то элементарное мне не хватает, но было бы здорово, если бы кто-то мог указать на Почему.
Символы в Clojure
Ответ 1
Общее введение:
Символы в любом Lisp используются как идентификаторы. Если вы собираетесь ссылаться на значение переменной, скажем, вам нужно иметь способ ее именовать; для каких символов. Помните, что код Lisp преобразуется во время чтения в структуры данных Lisp; Идентификаторы также должны быть представлены некоторой структурой данных, и это, оказывается, символ. При встрече с символом eval
отправляется на какую-либо операцию поиска имени.
Переходя от общих черт Lisp к Clojure деталям, поведение Clojure eval/compiler заключается в том, что, столкнувшись с символом, он принимает это имя как для локальной, так и для локальной переменной let
параметр функции или имя записи в пространстве имен. На самом деле в первой емкости могут использоваться только символы, не содержащие пространства имен (что означает символы формы foo
, а не some-namespace/foo
).
Пример с грубым наброском:
Для символа foo
, не относящегося к пространству имен, если найден параметр привязки/функции let
имени foo
, символ оценивает его значение. Если это не так, символ преобразуется в форму *ns*/foo
(*ns*
обозначает текущее пространство имен), и делается попытка найти запись с текущей встречей в *ns*
; если есть такая запись, ее значение возвращается, если нет, генерируется исключение.
Обратите внимание, что символ, подобный identity
, если он используется в пространстве имен quux
, будет разрешен до clojure.core/identity
через промежуточный этап, на котором обнаружена запись в quux/identity
; это обычно относится к clojure.core/identity
. Эта деталь реализации, о которой вы не интуитивно поняли, не учитывает, но которую я не могу не упомянуть при попытке объяснить это.
В соответствующем пространстве имен будет отображаться символ, который уже имеет пространство имен (что-то вроде zip/root
в пространстве имен, в котором refer
до clojure.zip
без use
").
Там добавлена сложность с макросами (которые могут возникать только в позиции оператора), но это не очень важно для поведения самих символов.
Vars vs Symbols:
Обратите внимание, что в Clojure символы не являются местами хранения - Vars. Поэтому, когда я говорю в приведенном выше примере, что символ просматривается в пространстве имен, я имею в виду, что eval
просматривает Var, названный символом, разрешенным к его форме с именем, и затем принимает значение этого. Специальная форма var
(часто сокращается до #'
) изменяет это поведение, так что сам объект Var возвращается. Механика разрешения от символа до варра не изменяется.
Заключительные замечания:
Обратите внимание, что все это означает, что символы только "привязаны" к объектам в том смысле, что eval
при оценке символа продолжает искать какой-то дополнительный объект. Сам символ не имеет "слота" или "поля" для привязки объекта к нему; любое впечатление о том, что символ привязан к некоторому объекту, обусловлен работой eval
. Это характеристика Clojure, так как в некоторых символах Лисса сами действуют как места хранения.
Наконец, можно использовать обычный механизм цитирования, чтобы предотвратить оценку символа: в 'foo
символ foo
не будет оцениваться (так что никакого поиска имени не будет выполнено); он будет возвращен без изменений.
В ответ на комментарий OP: попробуйте это для удовольствия:
(defmacro symbol?? [x]
(if (symbol? x)
true
false))
(def s 1)
(symbol? s)
; => false
(symbol?? s)
; => true
(symbol? 's)
; => true
(symbol?? 's)
; => false
Последнее объяснение: 's
является сокращением для (quote s)
; это структура списка, а не символ. Макрос работает с аргументами, переданными непосредственно, без оценки; поэтому в (symbol?? 's)
он фактически видит структуру списка (quote s)
, которая, конечно же, не является символом, хотя, если она передана в eval
, она будет оценивать один.
Ответ 2
Здесь может быть некоторая путаница в разных вариантах использования термина "символ" в Common Lisp и в Clojure.
В Common Lisp "символ" - это место в памяти, место, где могут храниться данные. "Значение" символа - это данные, хранящиеся в этом месте в памяти.
В Clojure "символ" - это просто имя. Это не имеет значения.
Когда компилятор Clojure встречает символ, он пытается разрешить его как
- имя класса Java (если символ содержит точку)
- локальный (как с параметрами "let" или function)
- a Var в текущем пространстве имен
- a Var, относящийся к другому пространству имен
Вар, как указал предыдущий плакат, представляет собой место хранения.
Есть веские причины, по которым Clojure отделяет Vars от символов. Во-первых, это позволяет избежать раздражения Common Lisp символов с автоматическим интернированием, которые могут "загрязнять" пакет с помощью нежелательных символов.
Во-вторых, Clojure Vars имеют специальную семантику относительно concurrency. У Var есть ровно одна "привязка корня", видимая для всех потоков. (Когда вы вводите "def", вы устанавливаете корневую привязку Var.) Изменения в Var, сделанные в потоке (используя "set!" Или "binding" ), видны только этому потоку и его дочерним элементам.