1

考虑这段代码 -

(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" 的记录。为什么已经定义的函数的定义会被后面定义的函数覆盖?

4

2 回答 2

3

这更像是对@ffriend 答案的补充,这是正确的。我只是认为,它需要一些澄清,这不太适合发表评论。

(defn f [x] body...)大致相当于(def f (fn [x] body...))。也就是说,创建一个函数并将其分配为 的值f。您的代码的主要问题在于defn(like 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-getterusingfn而不是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
于 2013-03-08T09:00:29.333 回答
2

来自Clojure 文档

使用符号名称和当前命名空间 ( ns ) 的值的命名空间创建和实习或定位一个全局变量。

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

你真正想要的是返回匿名闭包而不是定义函数:

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

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

于 2013-03-08T06:40:50.917 回答