user> (defn #^{ :xxx 1} foo [] "foo")
#'user/foo
user> (defn #^{ :xxx 2} bar [] "bar")
#'user/bar
I need to do something similar to this:
user> (map #(:xxx (meta %)) [foo bar])
(nil nil)
Basically accessing the meta data of a function of which I only know
the 'name'. (Because that is what my macro takes as one of its
parameters)
I know it works when I use the #' reader macro.
user> (map #(:xxx (meta %)) [#'foo #'bar])
(1 2)
But I'm using this in a bigger macro that takes a bunch of functions
as a parameter. Is there a way to make this work or should I
'translate' the functions that I take by name with (var foo)?
I must admin that I don't fully understand the difference between foo
and #'foo. That is probably why I'm making this beginner mistake :-)
S.
But I'm using this in a bigger macro that takes a bunch of functions
as a parameter. Is there a way to make this work or should I
'translate' the functions that I take by name with (var foo)?
Hmmm
user=> (var foo)
#'user/foo
user=> (meta (var foo))
{:ns #<Namespace user>, :name foo, :file "NO_SOURCE_FILE", :line
1, :arglists ([]), :xxx 1}
user=> ((var foo))
"foo"
I'll give this a try in my macro later today :-)
S.
> I must admin that I don't fully understand the difference between foo
> and #'foo. That is probably why I'm making this beginner mistake :-)
The difference takes some explanation. So without further ado...
Functions and Metadata: in Vivacious Gory Detail
================================================
There's three types of objects in play here: symbols, vars and functions:
(defn foo [])
(type foo)
=> user$foo_4703 (a class implementing IFn)
(type 'foo)
=> clojure.lang.Symbol
(type #'foo)
=> clojure.lang.Var
A symbol is just a name. A Var is an object that is named by a symbol
and "bound" to a value. Normally Clojure will "evaluate" symbols, for
example when you type this in the REPL:
foo
=> #<user$foo__4703 user$foo__4703@19f1a8a>
The way it does this is by first "resolving" the symbol foo in the
current namespace. A namespace is essentialy just a map from symbols to
vars. So after resolving, it then has a Var object. A Var, is as it's
name suggests, a variable. It consist of a name (symbol + namespace),
metadata map and a value (called the binding). There can actually be
multiple bindings (for thread-local variables and such) but normally
there is only one, the "root binding". So Clojure evaluates the var by
getting the value bound to it.
Now what if you ask Clojure to evaluate a vector of symbols? It
evaluates each symbol (first "resolving" to get a var and then taking
the binding) and gives you back a vector of function objects:
(def some-number 4)
[foo inc some-number]
=> [#<user$foo__4703 user$foo__4703@19f1a8a>
#<core$inc__4633 clojure.core$inc__4633@127e4be>
4]
Now macros. When Clojure sees something like this:
(bar foo inc some-number)
It will first resolve the symbol bar in the current namespace (in this
case the namespace is "user"). Remember resolving gives you a Var, so
in this case the Var #'user/foo. Clojure then looks at the metadata of
the Var to determine whether it is bound to a macro or a function.
Normally Clojure evaluates the arguments [foo inc some-number] producing
[#<user/foo> #<clojure.core/inc> 4] and then calls the binding value
(the actual function object) with them.
Alternatively if the var's metadata says it is bound to a macro, Clojure
doesn't evaluate the arguments. It just calls the binding with the
symbols 'foo 'inc and 'some-number. Clojure will then evaluate the
return value of the macro (usually another bunch of symbols and literals).
So when I write this:
(defmacro mymac [sym]
(println "symbol is:" sym)
sym)
(mymac foo)
=> symbol is: foo
#<user$foo__4703 user$foo__4703@19f1a8a>
Clojure passes mymac a symbol object. We print out the symbol "foo" and
then return it. Clojure then evaluates the return value (the symbol)
producing the actual function object that foo is bound to.
Now for metadata "on" functoins. When you write this:
(defn #^{:xxx 1} greet [] "hello")
The #^{...} syntax means that Clojure creates a list of two symbols
(defn and greet), and empty vector and a string "hello". The second
symbol greet has the metadata {:xxx 1} associated with it. This will be
macro-expanded into this:
(def #^{:xxx 1} greet (fn ([] "hello")))
The greet symbol still keeps it's metadata. Now the def special form:
1. Creates a var.
2. Copies the metadata {:xxx 1} from the symbol greet to the var.
3. Binds the var to the function (fn ([] "hello")).
4. Creates a mapping in the current namespace ("user") from the symbol
greet to the var.
Now notice the function object itself never has any metadata associated
with it? In fact normal Clojure function objects cannot have any metadata:
(with-meta (fn []) {:xxx 1})
=> [Thrown class java.lang.UnsupportedOperationException]
So now suppose we want something that achieves this:
(def #^{:xxx 2} groan (fn ([] "arrrgghhh")))
(get-xxx greet groan)
=> [1 2]
So we need to get at the vars for greet and groan. First thing to note
is that get-xxx has to be a macro, if it were a function greet and groan
would be evaluated to function objects, which don't have metadata. So
what we need is a macro that will take the greet and groan symbols,
resolve them to get vars and then lookup :xxx in the metadata of the
vars. So something like:
(defmacro get-xxx [& syms]
(vec (map #(get (meta (resolve %)) :xxx) syms)))
Now what if we want get-xxx to take a vector? Well no worries we just
change the signature:
(defmacro get-xxx [syms]
(vec (map #(get (meta (resolve %)) :xxx) syms)))
(get-xxx [greet groan])
=> [1 2]
But hang on, what's going on with this?
(let [fns [greet groan]]
(get-xxx fns))
=> java.lang.IllegalArgumentException: Don't know how to create ISeq
from: clojure.lang.Symbol
Remember that the arguments to macros aren't evaluated, so get-xxx is
being passed the symbol "fns" not a vector! Well what if use
macroexpand to explicitly pass it the vector?
(let [fns [greet groan]]
(macroexpand (list 'get-xxx fns)))
=> java.lang.ClassCastException: user$greet__5100 cannot be cast to
clojure.lang.Symbol
Another problem. The let creates a vector function objects not of
symbols. So to get this to work we need to quote greet and groan to
prevent them from being evaluated:
(let [fns ['greet 'groan]]
(macroexpand (list 'get-xxx fns)))
=> [1 2]
Or we can just quote the whole vector:
(let [fns '[greet groan]]
(macroexpand (list 'get-xxx fns)))
=> [1 2]
But at this point get-xxx may just as well have been a function:
(defn get-xxx-fn [syms]
(vec (map #(get (meta (resolve %)) :xxx) syms)))
(let [fns '[greet groan]]
(get-xxx-fn fns))
=> [1 2]
Now, that is one of the reasons why the first rule of Macro Club is:
Don't Write Macros. ;-)
Cheers,
Alex
Wow! Thank you so much for this excellent explanation! It totally
makes sense now :-)
S.
> Why? Well because #^ attaches the metadata to the next read form.This is subtle! It really feels like one of those things that will
> What's the next read form? It's 'greet. But in fact 'greet is just
> sugar for (quote greet). So we're actually affixing the metadata to a
> list containing two symbols (quote and greet). When the compiler
> evaluates (quote greet) it turns it into just the symbol greet and then
> throws the list (and thus our metadata) away.
still feel creepy another 50 years from now.
I'll have to meditate on this a bit.