Stefan Arentz wrote:

> 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__4...@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__4...@19f1a8a>
     #<core$inc__4633 clojure.core$inc__4...@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__4...@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

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to