Here's a few examples that demonstrate how &env and &form can be used.
(note: I'm using Clojure 1.2)
&env
By default &env is nil.
user=> (defmacro show-env [] (println &env))However, if any bindings exist, &env gives you the names of the bindings as the keys of a map.
#'user/show-env
user=> (show-env)
nil
user=> (let [band "zeppelin" city "london"] (show-env))Okay, now we're getting somewhere. What's a Compiler$LocalBinding? I'm not exactly sure, and I've never bothered to look into it. I've been told that the 'values' from &env may change in the future, so I wouldn't rely on them anyway. Since I can't rely on them, I haven't found the need to look into what they are.
{city #<LocalBinding clojure.lang.Compiler$LocalBinding@78ff9053>, band #<LocalBinding clojure.lang.Compiler$LocalBinding@525c7734>}
Back to the keys. They sure look like symbols.
user=> (defmacro show-env [] (println (keys &env)) (println (map class (keys &env))))As the example shows, they are definitely symbols. However, these symbols don't have a namespace.
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
(clojure.lang.Symbol clojure.lang.Symbol)
user=> (defmacro show-env [] (println (keys &env)) (println (map namespace (keys &env))))Since the symbols don't have a namespace there didn't seem to be much fun I could do with them; however, you can use the symbols in your macro to print the values, as the following example shows.
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
(nil nil)
user=> (defmacro show-env [] (println (keys &env)) `(println ~@(keys &env)))Printing the values of bindings can be a helpful trick while you are debugging.
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
london zeppelin
&form
&form can be used to get the original macro invocation.
user=> (defmacro show-form [] (println &form))Okay, not very interesting so far. It gets a bit more interesting when your macro takes a few arguments.
#'user/show-form
user=> (show-form)
(show-form)
user=> (defmacro show-form [a b] (println &form))So, you can get the arguments. Notice you can grab both 50 and 100.
#'user/show-form
user=> (show-form 50 100)
(show-form 50 100)
user=> (show-form a 100)
(show-form a 100)
user=> (defmacro show-form [a b] (println (next &form)))Interesting. So I have a few integers I can work with, if I wish. What about 'show-form'?
#'user/show-form
user=> (show-form 50 100)
(50 100)
user=> (defmacro show-form [a b] (println (map class (next &form))))
#'user/show-form
user=> (show-form 50 100)
(java.lang.Integer java.lang.Integer)
user=> (defmacro show-form [a b] (println (map class &form)))'show-form' is a symbol, as expected. Which brings us back to a previous example, shown again below.
#'user/show-form
user=> (show-form 50 100)
(clojure.lang.Symbol java.lang.Integer java.lang.Integer)
user=> (defmacro show-form [a b] (println (map class &form)))Okay, 'a' is also a symbol, unsurprising, but perhaps it's interesting since 'a' doesn't exist anywhere except in our invocation. You can probably do some interesting things here, like allow people to specify enum values and append the enum yourself.
#'user/show-form
user=> (show-form a 100)
(clojure.lang.Symbol clojure.lang.Symbol java.lang.Integer)
user=> (ns user (:import [java.util.concurrent TimeUnit]))Would you want to use &form instead of just using the arguments (stored in l)? Probably not. This isn't an exercise in what you should do, but it does demonstrate what you could do.
java.util.concurrent.TimeUnit
user=> (defmacro time-units [& l] (->> (next &form) (map (partial str "TimeUnit/")) (map symbol) (cons 'list)))
#'user/time-units
user=> (time-units SECONDS MILLISECONDS)
(#< SECONDS> #< MILLISECONDS>)
So &form must be returning a list, right?
user=> (defmacro show-form [a b] (println (map class &form)) (println (class &form)))A list. Correct.
#'user/show-form
user=> (show-form 50 100)
(clojure.lang.Symbol java.lang.Integer java.lang.Integer)
clojure.lang.PersistentList
And, I'm in a macro, so I can do anything I want with this list. Maybe I just want to print the arguments. Easy enough.
user=> (defmacro show-form [a b] `(println ~@(next &form)))Of course, there are a million things I could do. You get the idea.
#'user/show-form
user=> (show-form 50 100)
50 100
One other interesting thing to note is that &form has metadata.
user=> (show-form 50 100)Perhaps you don't care about line numbers, but they definitely can come in handy when you are writing a testing framework.
{:line 132}
I use &form in expectations and I believe LazyTest uses &env. I guess you never know what you're going to need...
I think the main reason that &form exists, is so that you can access metadata that was applied to the macro call. Otherwise that would get lost when the macro was expanded.
ReplyDeletePlease mention the version of Clojure you are using?
ReplyDeleteThanks Emeka, I've updated the post. I'm using Clojure 1.2
ReplyDelete