为什么最后一个def会覆盖此clojure代码中的任何先前defs?

时间:2013-03-08 06:27:47

标签: clojure

考虑这段代码 -

(defn make-getter
  [pred]
  (defn getter
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

(def meta-data-key? #(= (.getKey %) "META_DATA"))
(def not-meta-data-key? #(not (= (.getKey %) "META_DATA")))

(def get-type (make-getter meta-data-key?))
(def get-records (make-getter not-meta-data-key?))

HTreeMap是一个在JDBM HTree之上实现Map接口的java类。地图中有两种记录 - 密钥为“META_DATA”的记录和其他记录。 get-type函数应该返回的只是带有“META_DATA”键的条目,get-records应该返回除“META_DATA”键之外的所有内容。但是如果现在调用get-type,它甚至会返回那些getKey()!=“META_DATA”的记录。如果我将get-type和get-records的顺序更改为

(def get-records (make-getter not-meta-data-key?))
(def get-type (make-getter meta-data-key?))

然后两个函数只返回其getKey()==“META_DATA”的记录。为什么已定义的函数的定义会被后面定义的函数覆盖?

2 个答案:

答案 0 :(得分:3)

这是@ffriend的答案的补充,这是正确的。我只是想,它需要一些澄清,这不太适合评论。

(defn f [x] body...)大致相当于(def f (fn [x] body...))。也就是说,创建一个函数并将其指定为f的值。代码的主要问题在于defn(如def)返回符号而不是其值。那就是:

user=> (defn getter
           [db-name htree-name]
           (filter pred (HTreeMap. db-name htree-name)))
; => #'user/getter

(您将拥有当前的命名空间而不是user

因此,您对get-typeget-records的后续定义使其值为#'user/getter符号:

user=> getter
; => #<user$getter user$getter@f491ac9>
user=> get-type
; => #'user/getter
user=> get-records
; => #'user/getter

它们将有效地充当getter函数的别名。

除此之外,您每次调用getter时都会重新定义make-getter的值,并且您会明白为什么总能获得最新的定义。

解决此问题的正确方法是,使用make-getter代替fn定义defn

(defn make-getter
  [pred]
  (fn
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

或者您可以继续创建像这样的宏defgetter(未经过充分测试):

(defmacro def-getter
  [name pred]
  `(def ~name (make-getter [~pred])))

并像这样使用它:

#user=> (def-getter get-type meta-data-key?)
; => #'user/get-type
#user=> (def-getter get-records not-meta-data-key?)
; => #'user/get-records

答案 1 :(得分:2)

来自Clojure docs

  

使用符号名称和当前命名空间值的命名空间( ns )创建并实习或查找全局var。

因此,每次拨打make-getter时,都会定义新的全局功能。当然,在当前命名空间中可能只有一个具有此类名称的全局函数,因此所有先前的绑定都会被覆盖。

您真正想要的是返回匿名关闭而不是定义功能:

(defn make-getter
  [pred]
  (fn
    [db-name htree-name]
    (filter pred (HTreeMap. db-name htree-name))))

这种方式get-recordsget-types将使用2个不同的闭包对象(相同的匿名函数,但具有不同的绑定谓词)进行初始化。