在clojure中重新定义函数

时间:2012-01-04 21:22:11

标签: clojure

我想根据这些功能的一些元数据在启动时重新定义我的程序中的一些功能。

我是clojure的新手,所以我想要实现这一目标的惯用方法是什么。

我想要做的是使用缓存(如memcache)来缓存某些函数的结果(数据库结果)。以类似于memoize或contrib core.cache的方式,但我想根据定义缓存策略的元数据,将原始函数透明地重新定义到程序的其余部分。

Java库通常使用注释和代码生成来完成此任务。但我正在徘徊什么是惯用的方法来完成这个?

我已经在互联网上探讨了一些选项,但它们似乎并不太令人满意。 绑定不是我想要的,因为它只适用于当前线程。 其他选项似乎是使用一些我想避免的内部java函数,或者 绑定 ns 并使用eval重新引用函数。

我知道我可以用(keys(ns-publics'foo))列出一个命名空间中的潜在函数,但还没有探讨如何列出非公共函数以及如何列出可用的命名空间(当前已加载?) - 也许我可以使用名称空间加载钩子?

编辑: 这是我想到的一个小例子。 Wrap是一个根据origs元数据执行缓存的函数。示例中不存在缓存和元数据,wrap和orig都在同一名称空间中。

(defn orig []
    "OK")
(defn orig2 []
    "RES 2")

(defn wrap [f & args]
    (let [res (apply f args)]
        println "wrap" f args "=" res
        res))

(set! orig (wrap orig))
(set! orig2 (wrap orig2))

在评估最后两个表单后,orig和orig2应重新定义为使用包装版本。 不幸的是,我在REPL中收到以下错误:

java.lang.IllegalStateException:无法更改/建立root的绑定:orig with set(NO_SOURCE_FILE:0)

3 个答案:

答案 0 :(得分:8)

您可以再次使用def/defn,它将更改函数的定义(从技术上讲,它将编写一个使用相同名称的新定义。)

所以你可以这么做:

(def orig (wrap orig))
(def orig2 (wrap orig2))

另外,如果我理解你的意图:wrap应该返回一个函数,而不是结果。

(defn wrap [f]
  (fn [& args]
    (let [res (apply f args)]
      (println "wrap" f args "=" res)
      res)))

如果你看一下memoize标准函数,就会以这种方式运作。

答案 1 :(得分:4)

Clojure在vars中存储函数,使这种代码操作更容易。你可以只改变var的值来引用正确的函数。

如果你有一个潜在的缓存函数列表,那么你可以在它们自己的命名空间中定义它们,并有一些代码使用def来设置顶级绑定指向适当的一个。

(defn cache-all ...)
(defn cache-some ...)
(defn cache-none ...)

 (let [policy (get-current-policy)]
     (cond (= policy :all) (def cacher cache-all)
           (= policy :some) (def cacher cache-some)
           ...
           ))

如果您需要根据新输入实际定义函数,那么eval就是一种惯用法。

答案 2 :(得分:1)

将相应的函数作为变量传递给执行工作的代码。

e.g。

user=> (defn strategy-1 [input] (+ input 3))
#'user/strategy-1
user=> (defn strategy-2 [input] (- input 3))
#'user/strategy-2
user=> (defn fun-user [fn input] (fn input))
#'user/fun-user
user=> (fun-user strategy-1 5)  
8
user=> (fun-user strategy-2 5)
2

通过这种方式,您可以预先决定使用哪种策略,选择适当的函数,并将其传递给期望缓存函数执行正确操作的代码。然后代码可以只调用它作为参数接收的函数。