Macros
Important
Macros extend the language. Only use them if the problem cannot be solved with other language constructs.
Considering repetitive code for a three-start rating system:
(defn display [stars]
(case stars
3 "***"
2 "**"
1 "*"
""))
(defn verbalize [stars]
(case stars
3 (println "good")
2 (println "mediocre")
1 (println "bad")
(println "unknown")))
Write a macro using syntax quoting to insert junks code into a template:
(defmacro three-star-rating [stars three two one unknown]
`(case ~stars
3 ~three
2 ~two
1 ~one
~unknown))
The `
(backtick) quotes a template. The ~
character inserts the following identifier.
Rewrite the repetitive code using the macro:
(defn display [stars]
(three-star-rating stars "***" "**" "*" ""))
(defn verbalize [stars]
(three-star-rating
stars
(println "good")
(println "mediocre")
(println "bad")
(println "unknown")))
Expand a macro to inspect the generated code:
(macroexpand-1 '(three-star-rating 2 :yay :meh :nah :pff))
;; (clojure.core/case 2 3 :yay 2 :meh 1 :nah :pff)
Notice that macroexpand-1
expands macros once. The macroexpand
function expands the code recursively until all macros are expanded,
including (case
, cond
, etc.).
Exercises
Unless
Write a macro unless
that accepts both a condition and some code,
and, unlike when
, only executes the code if the condition does not
hold true.
Hint: Define the macro in terms of when
and not
.
Test: (unless (> 1 0) "math is not broken")
shall return nil
, and
(unless (> 0 1) "math is ok")
shall return "math is ok"
.
Arithmetic If
Write a macro arithmetic-if
that expects an integer n
, and three
code parameters pos
, zero
, and neg
, which are to be executed if
n
is positive, zero, or negative, respectively.
Hint: Write a template using cond
.
Test: (arithmetic-if 3 :+ :0 :-)
shall return :+
, (arithmetic-if 0 :+ :0 :-)
shall return :0
, and (arithmetic-if -7 :+ :0 :-)
shall return :-
.
Maybe
Write a macro maybe
that expects two parameters: code
to be
executed, and a probability p
of execution. The higher the
probability, the more likely the code is executed.
Hint: Use rand
to generate a random number between 0.0
and
1.0
. Execute the code if the random number is smaller than or equal
to the given probability.
Test: (maybe (println "*") 0.9)
should more likely output a *
character than (maybe (print "*") 0.1)
.