Overriding functions in clojure

I would like to redefine some functions in my program at startup according to some metadata of these functions.

I'm new to clojure, so I would like what an idiomatic way to accomplish this.

I would like to use a cache (e.g. memcache) to cache the results of some functions (database results). Similar to memoize or contrib core.cache, but I would like to redefine the original functions transparently for the rest of the program, according to the metadata that defines the cache policy.

Java libraries typically use annotations and code generation for this. But I'm wandering, what is an idiomatic way to achieve this?

I have studied several options on the Internet, but they do not seem too satisfactory. Binding is not what I want, because it only works with the current thread. Other options seem to use some internal java functions that I would like to avoid, or bind ns and override functions with eval.

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

EDIT: This is a small example of what I mean. Wrap is a function that caches according to origs metadata. Caching and metadata are missing in the 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 the last two forms, orig and orig2 must be redefined to use wrapped versions. Unfortunately, I get the following error in the REPL:

java.lang.IllegalStateException: Unable to change / set root binding: orig with set (NO_SOURCE_FILE: 0)

+4
source share
3 answers

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

So you can just do:

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

Also, if I understand your intent: wrap should return 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 standard memoize function, it will work that way.

+7
source

Clojure stores functions in vars to simplify this manipulation. you can simply change the var value to refer to the correct function.

if you have a list of potential caching functions, you can define them in your own namespace and have some code that def uses to set the top-level binding to a point in the corresponding 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 a function based on new input, then eval is an idomatic approach.

+3
source

Just pass the appropriate function as a variable to the code that does the job.

eg.

 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 

This way you can decide which strategy to use, select the appropriate function and pass it to code that expects the caching function to do the right thing. Then the code can simply call the function that it receives as an argument.

+1
source

Source: https://habr.com/ru/post/1389356/


All Articles