8
votes

Environment: Clojure 1.4

I'm trying to pull function metadata dynamically from a vector of functions.

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

(def funcs [funca funcb])

Now, retrieving the metadata in the REPL is (somewhat) straight-forward:

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 do a map to get the :ans, :tau-or-pi, or basic :name from the metadata, I get the exception:

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

After doing some more searching, I got the following idea from a posting 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) is putting the metadata on the Var in the def portion of the defn macro so that's why the simple (meta #'funca) is working, but is there a way to get the function metadata dynamically (like in the map example above)?

Maybe I'm missing something syntactically but if anyone could point me in the right direction or the right approach, that'd would be great.

Thanks.

3

3 Answers

3
votes

the expression #(meta #'%) is a macro that expands to a call to defn (actually def) which has a parameter named p1__1637# which was produced with gensym and the call to meta on that is attempting to use this local parameter as a var, since no var exists with that name you get this error.

If you start with a vector of vars instead of a vector of functions then you can just map meta onto them. You can use a var (very nearly) anywhere you would use a function with a very very minor runtime cost of looking up the contents of the var each 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 couple #'s to your original code and removing an extra trailing : should do the trick:

user> (defn #^{:tau-or-pi :pi} funca "doc for func a" {:ans 42} [x] (* x x))
#'user/funca
user> (defn #^{:tau-or-pi :tau} funcb "doc for func b" {:ans 43} [x] (* x x x))
#'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> 
2
votes

Recently, I found it useful to attach metadata to the functions themselves rather than the vars as defn does.

You can do this with good ol' def:

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

Here, the metadata has been attached to the functions and then those metadata-laden functions are bound to the vars.

The nice thing about this is that you no longer need to worry about vars when considering the metadata. Since the functions contain metadata instead, you can pull it from them directly.

(def funcs [funca funcb])

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

Obviously the syntax of def isn't quite as refined as defn for functions, so depending on your usage, you might be interested in re-implementing defn to attach metadata to the functions.

0
votes

I'd like to elaborate on Beyamor's answer. For some code I'm writing, I am using this:

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

Yes, it is a bit unwieldy to have two metadata maps. Here is why I do it:

  1. The first metadata attaches to the the-func var. So you can use (doc the-func) which returns:

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

    {:some-key :some-value}
    

In summary, this approach comes in handy when you want both docstrings in the REPL as well as dynamic access to the function's metadata.