Clojure macros

Личный сайт Go-разработчика из Казани

As with all Lisps, Clojure’s inherent homoiconicity gives you access to the full extent of the language to write code-generation routines called «macros». Macros provide a powerful way to tailor the language to your needs.

Be careful though. It’s considered bad form to write a macro when a function will do. Use a macro only when you need control over when or if the arguments to a form will be evaluated.

You’ll want to be familiar with Clojure. Make sure you understand everything in Clojure in Y Minutes.

1;; Define a macro using defmacro. Your macro should output a list that can 2;; be evaluated as clojure code. 3;; 4;; This macro is the same as if you wrote (reverse "Hello World") 5(defmacro my-first-macro [] 6 (list reverse "Hello World")) 7 8;; Inspect the result of a macro using macroexpand or macroexpand-1. 9;; 10;; Note that the call must be quoted. 11(macroexpand '(my-first-macro)) 12;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World") 13 14;; You can eval the result of macroexpand directly: 15(eval (macroexpand '(my-first-macro))) 16; -> (\d \l \o \r \W \space \o \l \l \e \H) 17 18;; But you should use this more succinct, function-like syntax: 19(my-first-macro) ; -> (\d \l \o \r \W \space \o \l \l \e \H) 20 21;; You can make things easier on yourself by using the more succinct quote syntax 22;; to create lists in your macros: 23(defmacro my-first-quoted-macro [] 24 '(reverse "Hello World")) 25 26(macroexpand '(my-first-quoted-macro)) 27;; -> (reverse "Hello World") 28;; Notice that reverse is no longer function object, but a symbol. 29 30;; Macros can take arguments. 31(defmacro inc2 [arg] 32 (list + 2 arg)) 33 34(inc2 2) ; -> 4 35 36;; But, if you try to do this with a quoted list, you'll get an error, because 37;; the argument will be quoted too. To get around this, clojure provides a 38;; way of quoting macros: `. Inside `, you can use ~ to get at the outer scope 39(defmacro inc2-quoted [arg] 40 `(+ 2 ~arg)) 41 42(inc2-quoted 2) 43 44;; You can use the usual destructuring args. Expand list variables using ~@ 45(defmacro unless [arg & body] 46 `(if (not ~arg) 47 (do ~@body))) ; Remember the do! 48 49(macroexpand '(unless true (reverse "Hello World"))) 50;; -> 51;; (if (clojure.core/not true) (do (reverse "Hello World"))) 52 53;; (unless) evaluates and returns its body if the first argument is false. 54;; Otherwise, it returns nil 55 56(unless true "Hello") ; -> nil 57(unless false "Hello") ; -> "Hello" 58 59;; Used without care, macros can do great evil by clobbering your vars 60(defmacro define-x [] 61 '(do 62 (def x 2) 63 (list x))) 64 65(def x 4) 66(define-x) ; -> (2) 67(list x) ; -> (2) 68 69;; To avoid this, use gensym to get a unique identifier 70(gensym 'x) ; -> x1281 (or some such thing) 71 72(defmacro define-x-safely [] 73 (let [sym (gensym 'x)] 74 `(do 75 (def ~sym 2) 76 (list ~sym)))) 77 78(def x 4) 79(define-x-safely) ; -> (2) 80(list x) ; -> (4) 81 82;; You can use # within ` to produce a gensym for each symbol automatically 83(defmacro define-x-hygienically [] 84 `(do 85 (def x# 2) 86 (list x#))) 87 88(def x 4) 89(define-x-hygienically) ; -> (2) 90(list x) ; -> (4) 91 92;; It's typical to use helper functions with macros. Let's create a few to 93;; help us support a (dumb) inline arithmetic syntax 94(declare inline-2-helper) 95(defn clean-arg [arg] 96 (if (seq? arg) 97 (inline-2-helper arg) 98 arg)) 99 100(defn apply-arg 101 "Given args [x (+ y)], return (+ x y)" 102 [val [op arg]] 103 (list op val (clean-arg arg))) 104 105(defn inline-2-helper 106 [[arg1 & ops-and-args]] 107 (let [ops (partition 2 ops-and-args)] 108 (reduce apply-arg (clean-arg arg1) ops))) 109 110;; We can test it immediately, without creating a macro 111(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5)) 112 113; However, we'll need to make it a macro if we want it to be run at compile time 114(defmacro inline-2 [form] 115 (inline-2-helper form)) 116 117(macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1))) 118; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1) 119 120(inline-2 (1 + (3 / 2) - (1 / 2) + 1)) 121; -> 3 (actually, 3N, since the number got cast to a rational fraction with /)

Further Reading

Writing Macros

Official docs

When to use macros?