Functions always evaluate their arguments before invoking the function, leading expressions to be evaluated in the familiar "inside out" pattern:
(/ (inc 3) (dec 3)) ; orig
(/ 4 2) ; args sent to `/` function
2 ; final result
The purpose of a macro is to avoid evaluating the arguments before calling the function, leading to expressions being evaluated "outside in". This allows one to compose macros as if each were a new language feature:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defmacro infix
[[l op r]]
`(~op ~l ~r))
(defmacro snoop
[& forms]
`(let [result# ~@forms]
(do
(println "snoop => " result#)
result#)))
(dotest
(spyx (infix (2 + 3)))
(spyx (infix (5 - 3)))
(newline)
(snoop (+ 1 2))
(snoop (infix (4 + 5)))
(newline)
(println :last
(infix ((snoop 4) + (snoop 5)))))
with result
(infix (2 + 3)) => 5
(infix (5 - 3)) => 2
snoop => 3
snoop => 9
snoop => 4
snoop => 5
:last 9
So that final example has an intermediate stage that looks like:
(println :last
(+ (snoop 4) (snoop 5)))
where can see that infix has been evaluated before the 2 snoop calls. This is exactly the strength of macros, and they would be useless without it.
You could maybe create a new macro that worked like:
(defmacro mmap
[fns coll]
`(let [comp-fn# (comp ~@fns)]
(mapv comp-fn# ~coll)))
(mmap [inc inc inc] [1 2 3]) => [4 5 6]
While the above mmap works, it is really an abuse of the macro system, and might fail if you give it something other than compile-time constants.
A better alternative for basic functions is simple memoization of a normal function:
(def do-stuff
(memoize ; will only execute on the first usage
(fn
[coll]
(mapv #(* 2 %) (mapv inc coll)))))
(do-stuff [1 2 3 4]) => [4 6 8 10]
If you think even a single call at runtime is too much, I would suggest manually running the "macro-like" code in a pre-compilation step and saving the result in new "source file" that could then be compiled as usual (perhaps you are pre-computing a bunch of prime numbers, for example).
The upshot is that the macro system is intended only for manipulating code in order to add new language features. Any other goal is better served using a different technique.
Regarding mapv
I also discovered mapv by accident and only after many months.
Be sure to bookmark the Clojure CheatSheet and always keep a browser tab open to it. Study it regularly until you can remember each of the functions. :)
Also be aware that some more obscure items are not listed there!
Update:
The "killer feature" of macros is to add new language features. In Java, et al, one can only add new libraries. As an example, spyx above is a macro very similar to the snoop example. For other examples see with-exception-default, vals->map, and it-> from the Tupelo library. In fact, many "core" Clojure features like the for expression or the and & or logical operators are actually macros that any application program could write (or modify!).
collto make sure macroexpansion occurs first, assuming you always have literal arguments. In case the collection is a value that is known only at runtime, you cannot apply a function on it during macroexpansion. - coredump