元数据

Clojure里面的元数据是附加到一个符号或者集合的一些数据,它们和符号或者集合的逻辑数据没有直接的关系。两个逻辑上一样的方法可以有不同的元数据。 下面是一个有关扑克牌的例子

(defstruct card-struct :rank :suit)

(def card1 (struct card-struct :king :club))
(def card2 (struct card-struct :king :club))

(println (== card1 card2)) ; same identity? -> false
(println (= card1 card2)) ; same value? -> true

(def card2 #^{:bent true} card2) ; adds metadata at read-time
(def card2 (with-meta card2 {:bent true})) ; adds metadata at run-time
(println (meta card1)) ; -> nil
(println (meta card2)) ; -> {:bent true}
(println (= card1 card2)) ; still same value despite metadata diff. -> true

一些元数据是Clojure内部定义的。比如 :private 它表示一个Var是否能被包外的函数访问。 :doc 是一个 Var 的文档字符串。 :test 元数据是一个Boolean值表示这个函数是否是一个测试函数。

:tag 是一个字符串类型的类名或者一个 Class 对象,表示一个Var在Java里面对应的类型,或者一个函数的返回值。这些被称为“类型提示” 。提供这些可以提高代码性能。如果你想查看你的clojure代码里面哪里使用反射来决定类型信息 -- 也就是说这里可能会有性能的问题, 那么你可以设置全局变量 *warn-on-reflection*true

一些元数据会由Clojure的编译器自动地绑定到Var对象。 :file 是定义这个 Var的文件的名字。 :line 是定义这个Var的行数。 :name 是一个Var的名字的 Symbol 对象。 :ns 是一个 Namespace 对象描述这个Var所在的名字空间。 :macro 是一个标识符标识这个符号是不是一个宏。 :arglist 是一个装有一堆vector的一个list, 表示一个函数所接受的所有的参数列表(前面在介绍函数的时候说过一个函数可以接受多个参数列表)。

函数以及宏,都是有一个 Var 对象来表示的, 它们都有关联的元数据。比如输入这个在REPL里面: (meta (var reverse)) 或者 ^#'reverse 。输出结果应该下面这些类似(为了好看我加了换行缩进)

{
  :ns #<Namespace clojure.core>,
  :name reverse,
  :file "core.clj",
  :line 630,
  :arglists ([coll]),
  :doc "Returns a seq of the items in coll in reverse order. Not lazy."
}

clojure.repl包里面的 source 函数, 利用元数据来获取一个指定函数的源代码,比如:

(source reverse)

上面代码的输出应该是:

(defn reverse
  "Returns a seq of the items in coll in reverse order. Not lazy."
  [coll]
    (reduce conj nil coll))