Bobbi Towers / Jun 26 2019
Clojure Brave Chapter 7
Anatomy of a Macro
(defmacro infix "Use this macro when you pine for the notation of your childhood" [infixed] (list (second infixed) (first infixed) (last infixed))) (infix (1 + 1))
2
(macroexpand (infix (1 + 1)))
List(3) (+, 1, 1)
Building Lists for Evaluation
Distinguishing Symbols and Values
(defmacro my-print [expression] (list let [result expression] (list println result) result))
user/my-print
Syntax Quoting
+
clojure.core/+
(+ 1 2)
List(3) (+, 1, 2)
(+ 1 2)
List(3) (clojure.core/+, 1, 2)
(+ 1 (inc 1))
List(3) (clojure.core/+, 1, 2)
(+ 1 (inc 1))
List(3) (clojure.core/+, 1, List(2))
(list + 1 (inc 1))
List(3) (+, 1, 2)
(+ 1 (inc 1))
List(3) (clojure.core/+, 1, 2)
Using Syntax Quoting in a Macro
(defmacro code-critic "Phrases are courtesy Hermes Conrad from Futurama" [bad good] (list do (list println "Great squid of Madrid, this is bad code:" (list quote bad)) (list println "Sweet gorilla of Manila, this is good code:" (list quote good)))) (code-critic (1 + 1) (+ 1 1))
(defmacro code-critic "Phrases are courtesy Hermes Conrad from Futurama" [bad good] (do (println "Great squid of Madrid, this is bad code:" (quote bad)) (println "Sweet gorilla of Manila, this is good code:" (quote good))))
user/code-critic
Refactoring a Macro and Unquote Splicing
(defn criticize-code [criticism code] (println criticism (quote code))) (defmacro code-critic [bad good] (do (criticize-code "Cursed bacteria of Liberia, this is bad code:" bad) (criticize-code "Sweet sacred boa of Western and Eastern Samoa, this is good code:" good)))
user/code-critic
(defmacro code-critic [bad good] (do (map (apply criticize-code %) [["Great squid of Madrid, this is bad code:" bad] ["Sweet gorilla of Manila, this is good code:" good]]))) (code-critic (1 + 1) (+ 1 1))
(+ (list 1 2 3))
List(2) (clojure.core/+, List(3))
(+ (list 1 2 3))
List(4) (clojure.core/+, 1, 2, 3)
(defmacro code-critic [good bad] (do (map (apply criticize-code %) [["Sweet lion of Zion, this is bad code:" bad] ["Great cow of Moscow, this is good code:" good]]))) (code-critic (1 + 1) (+ 1 1))
Things to Watch Out For
Variable Capture
(def message "Good job!") (defmacro with-mischief [& stuff-to-do] (concat (list let [message "Oh, big deal!"]) stuff-to-do)) (with-mischief (println "Here's how I feel about that thing you did: " message))
(def message "Good job!") (defmacro with-mischief [& stuff-to-do] (let [message "Oh, big deal!"] stuff-to-do)) (with-mischief (println "Here's how I feel about that thing you did: " message))
(defmacro without-mischief [& stuff-to-do] (let [macro-message (gensym message)] (let [macro-message "Oh, big deal!"] stuff-to-do (println "I still need to say: " macro-message)))) (without-mischief (println "Here's how I feel about that thing you did: " message))
(blarg# blarg#)
List(2) (blarg__939__auto__, blarg__939__auto__)
(let [name# "Larry Potter"] name#)
List(3) (clojure.core/let, Vector(2), name__944__auto__)
Double Evaluation
(defmacro report [to-try] (if to-try (println (quote to-try) "was successful:" to-try) (println (quote to-try) "was not successful:" to-try))) ;; Thread/sleep takes a number of milliseconds to sleep for (report (do (Thread/sleep 1000) (+ 1 1)))
(if (do (Thread/sleep 1000) (+ 1 1)) (println (do (Thread/sleep 1000) (+ 1 1)) "was successful:" (do (Thread/sleep 1000) (+ 1 1))) (println (do (Thread/sleep 1000) (+ 1 1)) "was not successful:" (do (Thread/sleep 1000) (+ 1 1))))
(defmacro report [to-try] (let [result# to-try] (if result# (println (quote to-try) "was successful:" result#) (println (quote to-try) "was not successful:" result#))))
user/report
Macros All the Way Down
(report (= 1 1))
(report (= 1 2))
(doseq [code [(= 1 1) (= 1 2)]] (report code))
(defmacro doseq-macro [macroname & args] (do (map (fn [arg] (list macroname arg)) args))) (doseq-macro report (= 1 1) (= 1 2))
Validation Functions
(def order-details {:name "Mitchard Blimmons" :email "mitchard.blimmonsgmail.com"})
user/order-details
(def order-details-validations {:name ["Please enter a name" not-empty] :email ["Please enter an email address" not-empty "Your email address doesn't look like an email address" (or (empty? %) (re-seq "@" %))]})
user/order-details-validations
(defn error-messages-for "Return a seq of error messages" [to-validate message-validator-pairs] (map first (filter (not ((second %) to-validate)) (partition 2 message-validator-pairs)))) (error-messages-for "" ["Please enter a name" not-empty])
List(1) ("Please enter a name")
(defn validate "Returns a map with a vector of errors for each key" [to-validate validations] (reduce (fn [errors validation] (let [[fieldname validation-check-groups] validation value (get to-validate fieldname) error-messages (error-messages-for value validation-check-groups)] (if (empty? error-messages) errors (assoc errors fieldname error-messages)))) {} validations)) (validate order-details order-details-validations)
Map {:email: List(1)}
(defmacro if-valid "Handle validation more concisely" [to-validate validations errors-name & then-else] (let [errors-name (validate to-validate validations)] (if (empty? errors-name) then-else))) (macroexpand (if-valid order-details order-details-validations my-error-name (println :success) (println :failure my-error-name)))
List(3) (let*, Vector(2), List(4))