Writing Macros
-
Upload
rueici-wang -
Category
Software
-
view
108 -
download
0
Transcript of Writing Macros
Writing MacrosChapter 8 of CLOJURE for the BRAVE and TRUE
( LINK : http://www.braveclojure.com/writing-macros/ )
2016/8/11 - ClojureTW 讀書會 @ 摩茲工寮, 台北
Macros Are Essential● Macros are an integral part of Clojure development
— they’re even used to provide fundamental operations.
● EXAMPLE: “ when ” = “ if ” + “ do ”
( macroexpand ‘(when boolean-expressionexpression-1expression-2expression-3))
; => (if boolean-expression; (do expression-1; expression-2; expression-3))
Anatomy of a MacroMacros receive unevaluated, arbitrary data structures as arguments and return data structures that Clojure evaluates
defmacro defn
(evaluated)
Building Lists for Evaluation● Macro writing is all about building a list for Clojure to evaluate
defmacro final list to evaluateunevaluated arguments
Building Lists for Evaluation● Macro writing is all about building a list for Clojure to evaluate
(defmacro infix-2 [[operand1 op operand2]] (list op operand1 operand2))
Building Lists for Evaluation● You’ll need to be extra careful about the difference
between a symbol and its value
Macro(building lists)
closed-box(list of symbol)
Evaluation
Building Lists for Evaluation● You’ll need to be extra careful about the difference
between a symbol and its value
(defmacro my-print-whoopsie [expression] (list let [result expression] (list println result) result))
EXCEPTION
Building Lists for Evaluation● You’ll need to be extra careful about the difference
between a symbol and its value
(defmacro my-print-whoopsie [expression] (list 'let ['result expression] (list 'println 'result) result))
OK!
Building Lists for EvaluationSingle Quoting vs. Syntax Quoting
'+; => +
`+; => clojure.core/+
Syntax quoting will always include the symbol’s full namespace → Help you avoid name collisions
Building Lists for EvaluationSingle Quoting vs. Syntax Quoting
'(+ 1 ~(inc 1)); => (+ 1 ~(inc 1))
`(+ 1 ~(inc 1)); => (clojure.core/+ 1 2)
full namespace evaluated instead of being quoted!
Building Lists for EvaluationSingle Quoting vs. Syntax Quoting
The other difference between quoting and syntax quoting is that the latter allows you to unquote forms using the tilde, ~
( q u o t e d )‘
( q u o t e d` ~ ( evaluated ) q u o t e d )
Using Syntax Quoting in a Macro
If you want your macro to return multiple forms for Clojure to evaluate, make sure to wrap them in a do.
(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))))
Refactoring a Macro and Unquote Splicing
(do
((clojure.core/println "criticism" '(1 + 1))
(clojure.core/println "criticism" '(+ 1 1))))
(do
(nil nil))EXCEPTIONNullPointerException
nil function nil argument
Unquote splicing was invented precisely to handle this kind of situation
Refactoring a Macro and Unquote Splicing
`(+ ~(list 1 2 3))
; => (clojure.core/+ (1 2 3))
`(+ ~@(list 1 2 3))
; => (clojure.core/+ 1 2 3) Great !
(defmacro code-critic
[{:keys [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]])))
do & mapso convenient !
Refactoring a Macro and Unquote Splicing
( q u o t e d` ~ ( evaluated ) q u o t e d )
( q u o t e d )‘
( q u o t e d` ~ ( evaluated ) q u o t e d )@Unquote
simple function
simple function+ do, map … etc.
Things to Watch Out For1. 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)); => Here's how I feel about that thing you did: Oh, big deal!
Things to Watch Out For1. Variable Capture
Uncorrelated Macro
letB -> C
function( input: A)( output: A)
function( input: A)
( B ->C)( output: A)
function( input: B)
( B ->C)( output: C)
B
B
B
C
Things to Watch Out For1. Variable Capture
(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))
; Exception: Can't let qualified name: user/message
Syntax quoting is designed to prevent you from accidentally capturing variables within macros.
Things to Watch Out For1. Variable Capture
(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))
; => Here's how I feel about that thing you did: Good job!
(gensym 'message)
; => message4763
Symbol prefix !
This example avoids variable capture by using gensym to create a new, unique symbol that then gets bound to macro-variable.
Things to Watch Out For1. Variable Capture
Uncorrelated Macro
gensym let B -> C
function( input: A)( output: A)
function( input: A)
( gensym B ->C)( output: A)
function( input: B)( B123 ->C)( output: B)
B
B
B
B
Things to Watch Out For1. Variable Capture
`(blarg# blarg#)
(blarg__2869__auto__ blarg__2869__auto__)
`(let [name# "Larry Potter"] name#)
; => (clojure.core/let [name__2872__auto__ "Larry Potter"]
name__2872__auto__)
Because this is such a common pattern, you can use an auto-gensym. Auto-gensyms are more concise and convenient ways to use gensyms
Things to Watch Out For
( q u o t e d` ~ ( evaluated ) q u o t e d )
( q u o t e d` ~ ( evaluated ) q u o t e d )@Unquote
+ do, map … etc.
( quoted` ~ ( evaluated )( let [ var # … ] autogem
Things to Watch Out For2. 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)))
12
3
Things to Watch Out For2. Double Evaluation
Repeated actions in Macro
let repeat 100 times
function( input: A)a lot of calc( output: A)
function( input: A)
a lot of repeating calc
( output: A)
B
B
B
function( input: A)
a lot of repeating calc
( output: A)
Waiting for calculation process ….
Things to Watch Out For2. 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)))
12
3
(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#))))
1 Double eval ? → let → Var capture ? → auto-gensym
Things to Watch Out For3. Macros All the Way Down
(doseq [code ['(= 1 1) '(= 1 2)]] (report code)); => code was successful: (= 1 1); => code was successful: (= 1 2)
(defmacro doseq-macro [macroname & args] `(do ~@(map (fn [arg] (list macroname arg)) args)))
(doseq-macro report (= 1 1) (= 1 2)); => (= 1 1) was successful: true; => (= 1 2) was not successful: false
report operating at macro expansion time, just can’t access those values.
To resolve this situation, you might write another macro
Things to Watch Out For3. Macros All the Way Down
● It’s easy to paint yourself into a corner, making it impossible to accomplish anything with run-of-the-mill function calls.
● They only really compose with each other, so by using them, you might be missing out on the other kinds of composition (functional, object-oriented) available to you in Clojure.
Macro Macro
Macro
Macro
Macro
Macro
Macro
Macro
Macro
Macro
Macro
Macro
SummaryDon’t listen to these prudes
— at least, not at first! Go out there and have a good time.
That’s the only way you’ll learn the situations where it’s appropriate
to use macros. You’ll come out the other side knowing how to use
macros with skill and panache.