2
votes

I'm new in learning and working with clojure so I've got a basic question on macros in clojure. I didn't find a case where you really need macros so I'm wondering if there is a real case where only a macro and no normal function or multimethod solves your problem.

Can someone show a simple example for this? I think I didn't understand the concept of macros in clojure.

2
This question may be too broad (too many possible answers). But, in general, you are correct. You don't need (new) macros. They can be very useful with judicious use, but aren't strictly necessary.A. Webb
@A.Webb, 'aren't strictly necessary' - macros are part of Lisp tool set that makes this language so expressive. At some level, one needs macros to write better programs in any Lisp, including Clojure.Mark Karpov
@Mark I don't disagree that they are useful, but a user of Clojure need never create a new macro and still produce highly expressive code. There is going to be regret and rework if you place macros in the top shelf of your toolbox.A. Webb
@A.Webb, I don't disagree that every tool should be used wisely :-)Mark Karpov

2 Answers

2
votes

Clojure macros take literal code whereas functions take evaluated code. In turn, macros are useful when you need to manipulate literal code. Literal code and evaluated code are equivalent except for two (very important) instances: symbols and expressions (maps, vectors, sets, strings, keywords, numbers, booleans, etcetera, will all "evalute to themselves").

user=> 1 ;evaluates to itself
1
user=> "abc" ;evaluates to itself
"abc"
user=> :xyz ;evaluates to itself
:xyz
user=> [1 "abc" :xyz] ;evaluates to itself
[1 "abc" :xyz]

As opposed to:

user=> (+ 1 2) ;an expression evaluates to not itself
3
user=> Math/PI ;a symbol evaluates to not itself
3.141592653589793
user=> + ;another example, a little weirder
#<core$_PLUS_ clojure.core$_PLUS_@417ffb28>

Let's say you wanted to create some-fn-or-macro to behave like this:

user=> (some-fn-or-macro (get {:a 10 :b 20} :a))
"(get {:a 10 :b 20} :a)"
user=> (some-fn-or-macro +)
"+"

You will not be able to do this with a function. Try it:

user=> (defn some-fn-or-macro [expr] (str expr))
#'user/some-fn-or-macro
user=> (some-fn-or-macro (get {:a 10 :b 20} :a))
"10"

What happened here? The argument to some-fn-or-macro (namely expr) got evaluated prior to being string-ized. However, if all we do is change the definition from a function to a macro, everything will be great:

user=> (defmacro some-fn-or-macro [expr] (str expr))
#'user/some-fn-or-macro
user=> (some-fn-or-macro (get {:a 10 :b 20} :a))
"(get {:a 10 :b 20} :a)"

That being said, if we take the original function definition again, and simply quote the argument on invocation, that also works:

user=> (defn some-fn-or-macro [expr] (str expr))
#'user/some-fn-or-macro
user=> (some-fn-or-macro '(get {:a 10 :b 20} :a))
"(get {:a 10 :b 20} :a)"

So you only ever need to write a macro if your use-case demands that arguments remain literal/unevaluated. If you have control over how your tool is used (which I'm guessing is always marginally true), you can decide to develop a function, and instruct users to quote arguments as necessary.

***Note: How I've used macros above might leave you in the dark about one extremely important fact of macros: their output gets evaluated. For example:

user=> (defmacro example-macro [] '(+ 1 2))
#'user/example-macro
user=> (example-macro)
3

You might think this is odd. There are a couple ways to make sense of it. Macros expect to take source code as input, so it's only natural that they'd give source code as output--and source code demands evaluation at some point. Actually, I tend to think of the difference between macros and functions as "shifted evaluation"--evaluation happens either "before" invocation, on the arguments (for functions); or "after" invocation, on the output (for macros).

2
votes

Important thing here is that macro does not evaluate its arguments and can be used for arbitrary transformation of source code.

Most basic examples of macros would be when and when-not macros:

(defmacro when
  "Evaluates test. If logical true, evaluates body in an implicit do."
  [test & body]
  `(if ~test (do ~@body)))

(defmacro when-not
  "Evaluates test. If logical false, evaluates body in an implicit do."
  [test & body]
  `(if test nil (do ~@body)))

Function won't work here, as it has to evaluate all its arguments before execution.

P.S. If you're interested in the topic and want to know more, see also this my answer. It is about Common Lisp, but it could be useful for you too. I also give a link to a cool Paul Graham's article at the end of the answer.

P.S.S If you want an example of a new useful macro, I would like to comment something of Paul Graham here:

It would be convenient here if I could give an example of a powerful macro, and say there! how about that? But if I did, it would just look like gibberish to someone who didn't know Lisp; there isn't room here to explain everything you'd need to know to understand what it meant.