19
votes

Say I have the following code:

(defn ^{:graph-title "Function 1"} func-1
  [x]
  (do-something-with x))

(defn get-graph-title 
  [func]
  (str
    ((meta func) :graph-title))) 

I expect this to return "Function 1", but it returns nil. I think this stems from the following difference, which I don't totally comprehend:

(meta func-1)
=>  {:ns some-ns-info, :name func-1}
(meta #'func-1)
=>  {:ns some-ns-info, :name func-1, :graph-title "Function 1"}

Can someone explain this to me?

4

4 Answers

21
votes

The metadata is attached to the var, not to the function.

Thus, to get the graph title, you have to get the entry :graph-title from the meta of the var. How do you like your macros ?

(defmacro get-graph-title
  [func]
  `(:graph-title (meta (var ~func))))

(get-graph-title func-1)
=> "Function 1"
34
votes

There's metadata on the function func-1, metadata on the Var #'func-1, and metadata on the symbol 'func-1. The Clojure reader macro ^ adds metadata to the symbol, at read time. The defn macro copies metadata from the symbol to the Var, at compile time.

Prior to Clojure 1.2, functions did not support metadata. In Clojure 1.2, they do, and defn also copies some standard Var metadata to the function:

Clojure 1.2.0
user=> (defn ^{:foo :bar} func-1 [] nil) 
#'user/func-1
user=> (meta func-1)
{:ns #<Namespace user>, :name func-1}
user=> (meta #'func-1)
{:foo :bar, :ns #<Namespace user>, :name func-1, ...

However, in current Clojure 1.3 snapshots, defn does not copy any metadata to the function:

Clojure 1.3.0-master-SNAPSHOT
user=> (defn ^{:foo :bar} func-1 [] nil) 
#'user/func-1
user=> (meta func-1)
nil
user=> (meta #'func-1)
{:foo :bar, :ns #<Namespace user>, :name func-1, ...

In general, if you want to get at the metadata of a definition, you want metadata on the Var.

2
votes

The metadata you specify on the symbol func-1 in your source code is copied to the var named func-1 by the def special form. See the documentation for def in http://clojure.org/special_forms

When you evaluate func-1 where that's a symbol bound to a var, you get the value of the var (which is the function object in this case). See http://clojure.org/vars

The function object itself does not automatically recieve the metadata manually specified on the symbol / var.

So, the information you want is not in the function at all. It's in the var, and you have to specify that you really want the var func-1 itself instead of its value. That's what (var func-1), and the equivalent short-cut #'func-1 does.

2
votes

While I wouldn't say this is bullet proof, I'll put it here for prosperity, in case for some reason, you only had the Fn object, and not the symbol or Var for it.

From what I know, when defn evaluates, it generates a class with name as per (munge fn-symbol). There is a function called demunge which will reverse this for us. Thus, from the class name of a Fn object, we can get the symbol back, and from the symbol, we can find the var back.

(-> (class some-fn)
    (print-str)
    (demunge)
    (symbol)
    (find-var)
    (meta))

Funnily enough, demunge can be found in both clojure.main and clojure.repl, and they do exactly the same thing, so feel free to require any of them to bring in demunge:

(require '[clojure.main :refer (demunge)])
;; or
(require '[clojure.repl :refer (demunge)])

WARNING:

As I said prior, first try to just do:

(meta #'some-fn)

Instead of what I'm showing in this answer, as it is the proper idiomatic mechanism.