forked from adambard/learnxinyminutes-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
clojure-macros.html.markdown
152 lines (117 loc) · 4.47 KB
/
clojure-macros.html.markdown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
---
language: "clojure macros"
filename: learnclojuremacros.clj
contributors:
- ["Adam Bard", "http://adambard.com/"]
---
As with all Lisps, Clojure's inherent [homoiconicity](https://en.wikipedia.org/wiki/Homoiconic)
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](/docs/clojure/).
```clojure
;; Define a macro using defmacro. Your macro should output a list that can
;; be evaluated as clojure code.
;;
;; This macro is the same as if you wrote (reverse "Hello World")
(defmacro my-first-macro []
(list reverse "Hello World"))
;; Inspect the result of a macro using macroexpand or macroexpand-1.
;;
;; Note that the call must be quoted.
(macroexpand '(my-first-macro))
;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")
;; You can eval the result of macroexpand directly:
(eval (macroexpand '(my-first-macro)))
; -> (\d \l \o \r \W \space \o \l \l \e \H)
;; But you should use this more succinct, function-like syntax:
(my-first-macro) ; -> (\d \l \o \r \W \space \o \l \l \e \H)
;; You can make things easier on yourself by using the more succinct quote syntax
;; to create lists in your macros:
(defmacro my-first-quoted-macro []
'(reverse "Hello World"))
(macroexpand '(my-first-quoted-macro))
;; -> (reverse "Hello World")
;; Notice that reverse is no longer function object, but a symbol.
;; Macros can take arguments.
(defmacro inc2 [arg]
(list + 2 arg))
(inc2 2) ; -> 4
;; But, if you try to do this with a quoted list, you'll get an error, because
;; the argument will be quoted too. To get around this, clojure provides a
;; way of quoting macros: `. Inside `, you can use ~ to get at the outer scope
(defmacro inc2-quoted [arg]
`(+ 2 ~arg))
(inc2-quoted 2)
;; You can use the usual destructuring args. Expand list variables using ~@
(defmacro unless [arg & body]
`(if (not ~arg)
(do ~@body))) ; Remember the do!
(macroexpand '(unless true (reverse "Hello World")))
;; ->
;; (if (clojure.core/not true) (do (reverse "Hello World")))
;; (unless) evaluates and returns its body if the first argument is false.
;; Otherwise, it returns nil
(unless true "Hello") ; -> nil
(unless false "Hello") ; -> "Hello"
;; Used without care, macros can do great evil by clobbering your vars
(defmacro define-x []
'(do
(def x 2)
(list x)))
(def x 4)
(define-x) ; -> (2)
(list x) ; -> (2)
;; To avoid this, use gensym to get a unique identifier
(gensym 'x) ; -> x1281 (or some such thing)
(defmacro define-x-safely []
(let [sym (gensym 'x)]
`(do
(def ~sym 2)
(list ~sym))))
(def x 4)
(define-x-safely) ; -> (2)
(list x) ; -> (4)
;; You can use # within ` to produce a gensym for each symbol automatically
(defmacro define-x-hygienically []
`(do
(def x# 2)
(list x#)))
(def x 4)
(define-x-hygienically) ; -> (2)
(list x) ; -> (4)
;; It's typical to use helper functions with macros. Let's create a few to
;; help us support a (dumb) inline arithmetic syntax
(declare inline-2-helper)
(defn clean-arg [arg]
(if (seq? arg)
(inline-2-helper arg)
arg))
(defn apply-arg
"Given args [x (+ y)], return (+ x y)"
[val [op arg]]
(list op val (clean-arg arg)))
(defn inline-2-helper
[[arg1 & ops-and-args]]
(let [ops (partition 2 ops-and-args)]
(reduce apply-arg (clean-arg arg1) ops)))
;; We can test it immediately, without creating a macro
(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))
; However, we'll need to make it a macro if we want it to be run at compile time
(defmacro inline-2 [form]
(inline-2-helper form)))
(macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1)))
; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1)
(inline-2 (1 + (3 / 2) - (1 / 2) + 1))
; -> 3 (actually, 3N, since the number got cast to a rational fraction with /)
```
### Further Reading
Writing Macros from [Clojure for the Brave and True](http://www.braveclojure.com/)
[http://www.braveclojure.com/writing-macros/](http://www.braveclojure.com/writing-macros/)
Official docs
[http://clojure.org/macros](http://clojure.org/macros)
When to use macros?
[http://dunsmor.com/lisp/onlisp/onlisp_12.html](http://dunsmor.com/lisp/onlisp/onlisp_12.html)