;; 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)) ;; -> (# "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 einee 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)