Macro metas (not meta macros)

78 views
Skip to first unread message

Alan

unread,
Mar 5, 2011, 3:08:48 AM3/5/11
to Clojure
Something is going on that I don't understand, in the interplay
between metadata, def'ing vars, and macros. I find myself writing a
lot of (defn myfn "doc" [x y z] (foo (bar (baz x y z)))), so I figured
I'd write a defcomp macro that I could call like (defcomp myfn
"doc" [x y z] foo bar baz), to make it clear what's going on (a
composition), without having to write each arg twice, and still get
IDE support for arglists and doc.

My first attempt was
(defmacro defcomp [name doc args & fs]
(let [fmeta {:doc doc :arglists (list args)}
sym (with-meta name fmeta)]
`(def ~sym (comp ~@fs))))

since my understanding was that metadata on the symbol is transfered
to the var in a def (as in (def ^:private foo 1)).

However, my simple testcase of (defcomp iterations "iterate some
stuff" [f start] rest iterate) fails. It macroexpands to (def
iterations (clojure.core/comp trim-seq rest iterate)), and if I
examine the metadata of the symbol in the expansion I see what I
expect:

user=> (-> '(defcomp ...) macroexpand second meta :arglists)
([f start])

But if I don't quote the defcomp, letting the compiler actually run
it, I get "Unable to resolve symbol: f in this context". Why is this
being eval'ed in the expansion context, when it's a quoted symbol in
the metadata of the symbol?

I was able to construct a working version:
(defmacro defcomp [name doc args & fs]
(let [fnmeta {:doc doc :arglists `'(~args)}]
`(def ~(with-meta name fnmeta) (comp ~@fs))))

But honestly that leaves me even more in the dark. I don't understand
how it works - the meta is being quoted too many times. Can anyone
explain to me what I'm missing?

Stuart Sierra

unread,
Mar 5, 2011, 3:40:18 AM3/5/11
to clo...@googlegroups.com
Just because it's metadata doesn't mean it won't get evaluated.  Consider the following:

    (set! *print-meta* true)

    (defmacro defmeta [name meta value] 
      `(def ~(with-meta name meta) ~value))

    (macroexpand '(defmeta foo {:key a} 42))
    ;;=> (def ^{:key a} foo 42)

    (defmeta foo {:key a} 42)
    ;; CompilerException java.lang.Exception: 
    ;; Unable to resolve symbol: a in this context

    (defmeta foo {:key 'a} 42)
    ^{:key a, ...} #'user/foo

Also, look at the source of the defn macro, which quotes the arglist during its expansion.

-Stuart Sierra
Reply all
Reply to author
Forward
0 new messages