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
153
154
155
156
157
158
159
160
161
|
---
language: "clojure macros"
filename: learnclojuremacros-de.clj
contributors:
- ["Adam Bard", "http://adambard.com/"]
translators:
- ["Dennis Keller", "https://github.com/denniskeller"]
lang: de-de
---
Wie mit allen Lisps besitzt auch Clojure die inhärente [Homoikonizität](https://en.wikipedia.org/wiki/Homoiconic),
die dir den vollen Zugang der Sprache gibt, um
Code-Generierungsroutinen zu schreiben. Diese werden "Macros" genannt.
Macros geben dir eine leistungsstarke Möglichkeit, die Sprache
an deine Bedürfnisse anzupassen.
Sei aber vorsichtig, es wird als schlechter Stil angesehen, wenn du
ein Macro schreibst, obwohl eine Funktion genauso gut funktionieren würde.
Verwende nur dann ein Macro, wenn du Kontrolle darüber brauchst, wann oder ob Argumente in einer Form evaluiert werden.
Wenn du mit Clojure vertraut sein möchtest, stelle sicher, dass du alles in [Clojure in Y Minutes](/docs/clojure/) verstehst.
```clojure
;; Definiere ein Macro mit defmacro. Dein Macro sollte eine Liste zurückgeben,
;; die als Clojure Code evaluiert werden kann.
;;
;; Dieses Macro ist das Gleiche, als ob du (reverse "Hallo Welt") geschrieben
;; hättest
(defmacro my-first-macro []
(list reverse "Hallo Welt"))
;; Inspiziere das Ergebnis eines Macros mit macroexpand oder macroexpand-1.
;;
;; Beachte, dass der Aufruf zitiert sein muss.
(macroexpand '(my-first-macro))
;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hallo Welt")
;; Du kannst das Ergebnis von macroexpand direkt auswerten.
(eval (macroexpand '(my-first-macro)))
; -> (\t \l \e \W \space \o \l \l \a \H)
;; Aber du solltest diese prägnante und funktionsähnliche Syntax verwenden:
(my-first-macro) ; -> (\t \l \e \W \space \o \l \l \a \H)
;; Du kannst es dir leichter machen, indem du die Zitiersyntax verwendest
;; um Listen in ihren Makros zu erstellen:
(defmacro my-first-quoted-macro []
'(reverse "Hallo Welt"))
(macroexpand '(my-first-quoted-macro))
;; -> (reverse "Hallo Welt")
;; Beachte, dass reverse nicht mehr ein Funktionsobjekt ist, sondern ein Symbol
;; Macros können Argumente haben.
(defmacro inc2 [arg]
(list + 2 arg))
(inc2 2) ; -> 4
;; Aber wenn du versuchst das mit einer zitierten Liste zu machen wirst du
;; einen Fehler bekommen, weil das Argument auch zitiert sein wird.
;; Um dies zu umgehen, bietet Clojure eine Art und Weise Macros zu zitieren: `
;; In ` kannst du ~ verwenden um in den äußeren Bereich zu kommen.
(defmacro inc2-quoted [arg]
`(+ 2 ~arg))
(inc2-quoted 2)
;; Du kannst die normalen destruktuierungs Argumente verwenden. Expandiere
;; Listenvariablen mit ~@.
(defmacro unless [arg & body]
`(if (not ~arg)
(do ~@body))) ; Erinnere dich an das do!
(macroexpand '(unless true (reverse "Hallo Welt")))
;; ->
;; (if (clojure.core/not true) (do (reverse "Hallo Welt")))
;; (unless) evaluiert und gibt body zurück, wenn das erste Argument falsch ist.
;; Andernfalls gibt es nil zurück
(unless true "Hallo") ; -> nil
(unless false "Hallo") ; -> "Hallo"
;; Die Verwendung Macros ohne Sorgfalt kann viel Böses auslösen, indem es
;; deine Variablen überschreibt
(defmacro define-x []
'(do
(def x 2)
(list x)))
(def x 4)
(define-x) ; -> (2)
(list x) ; -> (2)
;; Um das zu verhindern kannst du gensym verwenden um einen eindeutigen
;; Identifikator zu bekommen
(gensym 'x) ; -> x1281 (oder etwas Ähnliches)
(defmacro define-x-safely []
(let [sym (gensym 'x)]
`(do
(def ~sym 2)
(list ~sym))))
(def x 4)
(define-x-safely) ; -> (2)
(list x) ; -> (4)
;; Du kannst # innerhalb von ` verwenden um für jedes Symbol automatisch
;; ein gensym zu erstellen
(defmacro define-x-hygienically []
`(do
(def x# 2)
(list x#)))
(def x 4)
(define-x-hygienically) ; -> (2)
(list x) ; -> (4)
;; Es ist üblich, Hilfsfunktionen mit Macros zu verwenden. Lass uns einige
;; erstellen, die uns helfen , eine (dumme) arithmetische Syntax
;; zu unterstützen
(declare inline-2-helper)
(defn clean-arg [arg]
(if (seq? arg)
(inline-2-helper arg)
arg))
(defn apply-arg
"Bekomme die Argumente [x (+ y)], gebe (+ x y) zurück"
[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)))
;; Wir können es sofort testen, ohne ein Macro zu erstellen
(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))
; Allerdings, brauchen wir ein Macro, wenn wir es zur Kompilierungszeit
; ausführen wollen
(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 (eigentlich, 3N, da die Zahl zu einem rationalen Bruch mit / umgewandelt wird)
```
### Weiterführende Literatur
[Macros schreiben](http://www.braveclojure.com/writing-macros/)
[Offiziele Docs](http://clojure.org/macros)
[Wann verwendet man Macros?](https://lispcast.com/when-to-use-a-macro/)
|