On Nov 20, 12:29 pm, Rock <
rocco.ro...@gmail.com> wrote:
> I was just reading the Macro section in the WikiBook. Regarding the
> following piece of code,
>
> `(let [~'frame ~frame]
> (.setTitle ~'frame ~title)
> (.setVisible ~'frame)
> ~'frame)
>
> I was what the difference might be with respect to this:
>
> `(let [frame ~frame]
> (.setTitle frame ~title)
> (.setVisible frame)
> frame)
>
Expanding these shows the difference:
and their values frame = 0, title = 1;
the first one becomes
(clojure.core/let [frame 0]
(user/.setTitle frame 1)
(user/.setVisible frame) frame)
Now, this is a valid clojure form (ignoring that integers don't
have .setTitle or .setVisible) that could in theory be evaled.
This kind of expansion should be treated with care. It *captures* the
name frame from the outer scope, shadowing its definition with the
let. This is a dangerous thing to do, and in case it's needed, it
should be clearly documented what variable names are captured. In this
case, you know the context this expansion is done in, so it's not a
problem, but that is not always the case with macros.
Using ~' in a macro shows that you explicitly want to capture
something.
The second one becomes:
(clojure.core/let [user/frame 0]
(user/.setTitle user/frame 1)
(user/.setVisible user/frame) user/frame)
Notice how frame and title are qualified with the namespace.
The crucial difference is:
Namespace-qualified names can't be let.
This means that this will always throw an exception when evaluated.
Namespace qualifying is what makes clojure macros hygienic. That is,
guaranteed not to capture names unless done explicitly with ~' or
through other capturing macros.
the correct way to write this expansion would have been:
(let [my-frame (gensym)] ;notice, not in ` yet, so even if this were
in a macro it will not appear in the macro expansion.
`(let [~my-frame ~frame]
(something-or-other)
~my-frame))
here, the *value* of my-frame is a generated symbol; something
guaranteed to be unique. To that name, we bind the value of frame: the
expansion will become:
(clojure.core/let [G__1278 0]
(user/something-or-other)
G__1278)
which is safe.
Furthermore, another, easier way to produce a similar expansion is to
use automatic gensyms; in a ` form, any symbol prefixed with # is
uniquely expanded within that form (autogensyms don't nest):
`(let [myframe# ~frame]
(something-or-other)
myframe#) ; note, no ~. ~myframe# would mean 0
the expansion is similar to the above:
(clojure.core/let [myframe__1282 0]
(user/something-or-other)
myframe__1282)
> I'm coming from Common Lisp. I've got to admit that, as much as I've
> always loved using macros, I have always felt that BACKQUOTE (or
> syntax-quote in this case) can be confusing, except for the simpler
> cases. I mean, when you get to nested backquotes, for instance, it can
> become pretty complicated (as stated by Paul Graham as well).
Nested syntax-quotes can indeed cause problems with gensyms or just
readability. Fortunately, even though macros may seem like magic, in
the end they are just functions that return forms (clojure code). This
means you can make helper functions (with defn) that return simpler
forms (often by using syntax-quote themselves) and use those helpers
in the macro to produce more complex forms:
(defn helper [x method]
`(~method ~x))
`(do ~@(map helper '(obj another more)
'(.mutate .mutate .mutateMore)))
which will expand into
(do (.mutate obj) (.mutate another) (.mutateMore more))
> Is there any chance of getting a more thourough explanation within
> Clojure? I'm convinced this is an important issue, especially for
> those coming from the likes of Java, Python, Ruby, etc...
I'm not a macro expert myself, but I hope this will help you and
others as well.
It's good to spend some time simply writing macros and using
'macroexpand to see what kind of code they produce. I don't think
there is any other way to learn to write macros.
--
Jarkko