考虑这段代码 -
(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”的记录。为什么已定义的函数的定义会被后面定义的函数覆盖?
答案 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-type
和get-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-records
和get-types
将使用2个不同的闭包对象(相同的匿名函数,但具有不同的绑定谓词)进行初始化。