В ближайшем будущем, когда полезно определить несколько символов с одинаковым именем, но разными метаданными? - PullRequest
4 голосов
/ 21 февраля 2012

В clojure два символа a и b могут иметь одно и то же имя, но разные метаданные. Символы тогда =, но не identical?.

Например:

(def a (with-meta 'cool {:real-name 'unknown}))
(def b (with-meta 'cool {:real-name 'undefined}))
(identical? a b); false
(= a b); true 

Кажется, это очень сильно. Я хотел бы увидеть пример использования этой функции языка в реальной жизни.

Пожалуйста, поделитесь своими оригинальными мыслями.

Ответы [ 3 ]

3 голосов
/ 22 февраля 2012

Боюсь, этот вариант использования является идеей Рича, а не моей, но, возможно, он будет интересен:

Сам язык использует эту функцию в def формах (и удобной упаковке макросов defвключая defn и defmacro), поскольку метаданные, прикрепленные к символам, именующим создаваемые переменные, передаются самим переменным.Затем он может использоваться компилятором и различными инструментами.

Можно отметить, что defn & Co. дополнительно может принимать аргумент карты атрибутов, который объединяется с метаданными Vars, поэтому используйте метаданные на символическом имени.не является необходимым с точки зрения пользователя.Обычный def, однако, этого не делает, а внутри defn расширяется до формы def с метаданными, поступающими из карты атрибутов, прикрепленной к самому имени Var.(Ради полноты можно изменить карту метаданных Var на отдельном этапе после ее создания, но это не то, что происходит в defn.)

Все это не скрыто от программиставообще, кстати.Напротив, прикрепление метаданных к символу «вручную» во многих случаях является более кратким, чем использование карты атрибутов (возможно, можно даже сказать идиоматичнее).Собственная стандартная библиотека Clojure использует этот стиль (и я имею в виду post-bootstrap) - например, clojure.core и clojure.string определяют Vars с именем replace, но только последний помечен с типом возвращаемого значения (а именно String):

;;; clojure.core
(defn replace
  ...)

;;; clojure.string
(defn ^String replace
  ...)

^String здесь преобразуется в {:tag String}, который присоединяется к символу replace во время чтения и позже используется компилятором (как подсказка типа).Другие применения включают в себя пометку Vars как приватную с ^:private (в Clojure 1.3, ^{:private true} в 1.2), присоединение строк документации к Vars, созданным с помощью простого def (единственный способ сделать это в 1.2; 1.3 позволяет использовать дополнительный аргумент docstring,но ^{:doc "..."} все еще используется) и т. д.

Точно так же в ClojureScript у вас могут быть две функции с именем foo, только одна из которых должна быть экспортирована (конечно, в разных пространствах имен).В этом случае вы скажете

(defn ^:export foo ...)

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

(defn foo ...)

в другом;^:export переводится в ^{:export true} во время чтения, это объединяется с метаданными этого вхождения символа foo, а затем читается и обрабатывается компилятором ClojureScript.

3 голосов
/ 21 февраля 2012

Я делаю это в некотором программном обеспечении, которое я взламываю для вероятностного анализа случайных процессов. Вы можете сказать мне, «настоящая жизнь» или нет. В целом метаданные полезны для предоставления контекста о наборах или элементах набора, который не зависит от их значения. Например, я могу представить себе универсальный набор, набор реалов и элемент, содержащийся в реалах

(def Reals (with-meta 'Reals {:universe 'U}))
(def x (with-meta 'x {:universe Reals}))

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

(def known-integers #{'x 'y 'z})
(defn find-known-integers [& s] (filter #(contains? known-integers %) (with-meta s {:universe 'Integers})))

Теперь я могу написать

user=> (find-known-integers 'x 'a)
(x)

Но у меня также есть

user=> (= x (first (find-known-integers 'x 'a)))
true
user=> (identical? x (first (find-known-integers 'x 'a)))
false

так что, хотя оба элемента имеют одинаковое значение, они не считаются идентичными, поскольку известно, что версия, возвращаемая функциями find-known-integer, содержится в гораздо меньшей вселенной, чем оригинал. Конечно, эта информация о содержании в некотором смысле является «субъективной», поэтому ее полезно рассматривать как метаданные, поскольку разные анализы могут давать разные (и, возможно, даже противоречивые) результаты для переменной.

2 голосов
/ 22 февраля 2012

Имейте в виду, что identical? фактически сравнивает не значения + метаданные, а ссылки на объекты, поэтому identical? - это то же самое, что оператор == в Java.Из-за этого у вас будет:

=> (def a 'sym)
=> (def b 'sym)
=> (= a b)
true
=> (identical? a b)
false

С помощью этого метода можно различать переменные с разными метаданными (потому что внутренне они кажутся разными объектами), но не наоборот: вы не можете сделать вывод, что переменные содни и те же метаданные всегда будут identical?:

user=> (def d (with-meta 'sym { :a :b }))
#'user/d
user=> (def e (with-meta 'sym { :a :b }))
#'user/e
user=> (= d e)
true
user=> (identical? d e)
false
...