在clojure中,何时定义具有相同名称但不同元数据的多个符号是有用的?

时间:2012-02-21 17:57:31

标签: clojure lisp metadata

在clojure中,两个符号ab可能具有相同的名称,但元数据不同。然后符号为=,但不是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 个答案:

答案 0 :(得分:3)

我在一些软件中这样做,我正在黑客攻击随机过程的概率分析。你可以告诉我是否是“现实生活”'。通常,元数据对于提供与集合中的集合或元素无关的集合或元素的上下文非常有用。例如,我可以想象一个通用集,实数集和实数中包含的元素

(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-integers函数返回的版本包含在比原始函数小得多的Universe中。当然,关于遏制的这些信息在某种意义上是“主观的”,这就是将其视为元数据有用的原因,因为不同的分析可能会产生不同的(甚至可能是相互矛盾的)结果。变量

答案 1 :(得分:3)

我担心这个用例是Rich的想法而不是我的想法,但也许它会引起人们的兴趣:

语言本身在def表单(以及包含defdefn的便捷宏包括defmacrodefn)中使用此功能,因为附加到命名Vars的符号的元数据正在创建的转移到Vars本身。然后它可以被编译器和各种工具使用。

有人可能会指出def&可选地,可以获取合并到Vars元数据中的属性映射参数,因此从用户的角度来看,不需要使用符号名称上的元数据。但是,普通defn没有,并且内部def扩展为defn表单,其中元数据来自附加到Var名称本身的属性映射。 (为了完整起见,可以在创建后的单独步骤中修改Var的元数据映射,但这不是clojure.core中发生的情况。)

顺便说一下,所有这些都不是程序员所隐藏的。相反,将元数据“手动”附加到符号在许多情况下比使用属性映射更简洁(也许甚至可以说更惯用)。 Clojure自己的标准库使用这种风格(我的意思是后引导) - 例如,clojure.stringreplace都定义了名为String的Vars,但只有后者标记为返回值类型(即;;; clojure.core (defn replace ...) ;;; clojure.string (defn ^String replace ...) ):

^String

{:tag String}此处转换为replace,它在读取时附加到符号^:private,稍后由编译器使用(作为类型提示)。其他用途包括将Vars标记为私有^{:private true}(在Clojure 1.3中,def在1.2中),将文档字符串附加到使用普通^{:doc "..."}创建的Vars(在1.2中执行此操作的唯一方法; 1.3;允许额外的docstring参数,但仍然使用foo等等。

类似地,在ClojureScript中,您可以有两个名为(defn ^:export foo ...) 的函数,其中只有一个应该被导出(当然,它们位于不同的名称空间中)。在那种情况下,你会说

(defn foo ...)

在一个名称空间中

^:export

在另一个; ^{:export true}在读取时被转换为foo,它会合并到符号{{1}}的此次出现的元数据中,然后由ClojureScript编译器读取并执行。

答案 2 :(得分:2)

请注意,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