Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

redefining functions in clojure

Tags:

clojure

I would like to redefine some functions in my program on startup according to some metadata on those functions.

I am new to clojure so I would like what is the idiomatic way to accomplish this.

What I would like to do is use a cache (like memcache) to cache results of some functions (database results). In similar way like memoize or contrib core.cache, but i would like to redefine original functions transparently to the rest of the program, according to metadata which defines the caching policy.

Java libraries usually use annotations and code generation to accomplish this. But I am wandering what is idiomatic clojure way of accomplishing this?

I have explored a few options on the internet but they don't seem too satisfactory. Binding is not what I want because it only works on the current thread. Other options seems to be using some internal java functions which I would like to avoid, or binding ns and rerefining functions with eval.

I understand that i can list the potential functions in one namespace with (keys (ns-publics 'foo)), but have not explored yet how to list non public functions and how to list available namespaces (currently loaded?) - maybe there is namespace loading hook i can use?

EDIT: This is a small example of what I have in mind. Wrap is a function that would perform caching according to origs metadata. Caching and metadata are absent from example, and both wrap and orig are in the same namespace.

(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))

After evaluating last two forms orig and orig2 should be redefined to use wrapped versions. Unfortunately I get the following error in REPL:

java.lang.IllegalStateException: Can't change/establish root binding of: orig with set (NO_SOURCE_FILE:0)

like image 670
user744959 Avatar asked Dec 12 '22 06:12

user744959


2 Answers

You can use def/defn again and it will change the definition of the function (technically it will write a new definition which uses the same name).

So you could just do:

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

Also, if I understand your intention: wrap should be returning a function, not a result.

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

If you look at the memoize standard function it works in precisely this way.

like image 165
mange Avatar answered Dec 14 '22 19:12

mange


Clojure stores functions in vars to make this sort of code manipulation easier. you can just change the value of the var to reference the proper function.

if you have a list of potential caching functions then you can define them in their own namespace and have a bit of code that uses def to set the top level binding to point at the appropriate one.

(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)
           ...
           ))

if you need to actually define the function based on new input then eval is the idomatic approach.

like image 39
Arthur Ulfeldt Avatar answered Dec 14 '22 21:12

Arthur Ulfeldt