Retrieve Clojure function metadata dynamically
Asked Answered
P

3

8

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.

Paranoia answered 28/12, 2012 at 18:10 Comment(0)
P
2

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.

Prosciutto answered 28/12, 2012 at 20:37 Comment(1)
I thought about doing the def outside of the defn macro as well since it appears that in 1.4 the defn macro puts all of the metadata on the var. I'm leaning towards your approach since it doesn't add the var step. Thanks.Paranoia
E
3

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> 
Evadne answered 28/12, 2012 at 19:49 Comment(1)
I had thought about prefacing the functions when I built the function vector but I wanted to know if there was a way to access the metadata from the function itself. Your answer works, though. Thanks, Arthur.Paranoia
P
2

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.

Prosciutto answered 28/12, 2012 at 20:37 Comment(1)
I thought about doing the def outside of the defn macro as well since it appears that in 1.4 the defn macro puts all of the metadata on the var. I'm leaning towards your approach since it doesn't add the var step. Thanks.Paranoia
W
0

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.

Wont answered 22/5, 2013 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.