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).