Get Clojure Dynamic Metadata

Environment: Clojure 1.4

I am trying to derive function metadata dynamically from a function vector.

(defn #^{:tau-or-pi: :pi} funca "doc for func a" {:ans 42} [x] (* xx)) (defn #^{:tau-or-pi: :tau} funcb "doc for func b" {:ans 43} [x] (* xxx)) (def funcs [funca funcb]) 

Now getting the metadata in the REPL is (somewhat) straightforward:

 user=>(:tau-or-pi (meta #'funca)) :pi user=>(:ans (meta #'funca)) 42 user=>(:tau-or-pi (meta #'funcb)) :tau user=>(:ans (meta #'funcb)) 43 

However, when I try to make a map to get :ans :tau-or-pi or base :name from metadata, I get an exception:

 user=>(map #(meta #'%) funcs) CompilerException java.lang.RuntimeException: Unable to resolve var: p1__1637# in this context, compiling:(NO_SOURCE_PATH:1) 

After several searches, I got the following idea from a publication in 2009 ( https://groups.google.com/forum/?fromgroups=#!topic/clojure/VyDM0YAzF4o ):

 user=>(map #(meta (resolve %)) funcs) ClassCastException user$funca cannot be cast to clojure.lang.Symbol clojure.core/ns-resolve (core.clj:3883) 

I know that the defn macro (in Clojure 1.4) places metadata in Var in the def part of the defn macro, so if it works simple (meta #'funca) but is there a way to get the function metadata dynamically (for example, in the map example)?

I might have missed something syntactically, but if someone could point me in the right direction or the right approach, that would be great.

Thanks.

+8
function clojure metadata
source share
3 answers

Lately, it has been useful to me to attach metadata to the functions themselves, and not to vars, as defn does.

You can do it with good ol ' def :

 (def funca ^{:tau-or-pi :pi} (fn [x] (* xx))) (def funcb ^{:tau-or-pi :tau} (fn [x] (* xxx))) 

Here metadata was bound to functions, and then this loaded function metadata was bound to vars.

The nice thing is that you no longer have to worry about your options when viewing metadata. Since functions contain metadata, you can directly extract them from them.

 (def funcs [funca funcb]) (map (comp :tau-or-pi meta) funcs) ; [:pi :tau] 

Obviously, the def syntax is not as sophisticated as defn for functions, so depending on your use, you might be interested in reinstalling defn to attach metadata to functions.

+2
source share

expression #(meta #'%) is a macro that extends to defn (actually def ), which has a parameter called p1__1637 # that was created using gensym , and a meta call on the one that is trying to use this local parameter like var, since var does not exist with this name, you get this error.

If you start with the var vector instead of the function vector , then you can just overlay the meta on them. You can use the var variable (almost) anywhere you would use a function with very little cost when you search for the contents of var every time it is called.

 user> (def vector-of-functions [+ - *]) #'user/vector-of-functions user> (def vector-of-symbols [#'+ #'- #'*]) #'user/vector-of-symbols user> (map #(% 1 2) vector-of-functions) (3 -1 2) user> (map #(% 1 2) vector-of-symbols) (3 -1 2) user> (map #(:name (meta %)) vector-of-symbols) (+ - *) user> 

so adding a #' pair to your source code and removing the extra trailing: should do the trick:

 user> (defn #^{:tau-or-pi :pi} funca "doc for func a" {:ans 42} [x] (* xx)) #'user/funca user> (defn #^{:tau-or-pi :tau} funcb "doc for func b" {:ans 43} [x] (* xxx)) #'user/funcb user> (def funcs [#'funca #'funcb]) #'user/funcs user> (map #(meta %) funcs) ({:arglists ([x]), :ns #<Namespace user>, :name funca, :ans 42, :tau-or-pi :pi, :doc "doc for func a", :line 1, :file "NO_SOURCE_PATH"} {:arglists ([x]), :ns #<Namespace user>, :name funcb, :ans 43, :tau-or-pi :tau, :doc "doc for func b", :line 1, :file "NO_SOURCE_PATH"}) user> (map #(:tau-or-pi (meta %)) funcs) (:pi :tau) user> 
+3
source share

I would like to dwell on the answer of Beyamor . For some code I'm writing, I use this:

 (def ^{:doc "put the-func docstring here" :arglists '([x])} the-func ^{:some-key :some-value} (fn [x] (* xx))) 

Yes, it's a little cumbersome to have two metadata maps. This is why I do it:

  • The first metadata is attached to the-func var. So you can use (doc the-func) , which returns:

     my-ns.core/the-func ([x]) put the-func docstring here 
  • The second metadata is attached to the function itself. This allows you to use (meta the-func) to return:

     {:some-key :some-value} 

Thus, this approach is useful when you want both docstrings in REPL and dynamic access to function metadata.

0
source share

All Articles