Writing Macros

28
Writing Macros Chapter 8 of CLOJURE for the BRAVE and TRUE ( LINK : http://www.braveclojure.com/writing-macros/ ) 2016/8/11 - ClojureTW 讀書會 @ 摩茲工寮, 台北

Transcript of Writing Macros

Page 1: Writing Macros

Writing MacrosChapter 8 of CLOJURE for the BRAVE and TRUE

( LINK : http://www.braveclojure.com/writing-macros/ )

2016/8/11 - ClojureTW 讀書會 @ 摩茲工寮, 台北

Page 2: Writing Macros

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))

Page 3: Writing Macros

Anatomy of a MacroMacros receive unevaluated, arbitrary data structures as arguments and return data structures that Clojure evaluates

defmacro defn

(evaluated)

Page 4: Writing Macros

Building Lists for Evaluation● Macro writing is all about building a list for Clojure to evaluate

defmacro final list to evaluateunevaluated arguments

Page 5: Writing Macros

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))

Page 6: Writing Macros

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

Page 7: Writing Macros

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

Page 8: Writing Macros

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!

Page 9: Writing Macros

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

Page 10: Writing Macros

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!

Page 11: Writing Macros

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 )

Page 12: Writing Macros

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))))

Page 13: Writing Macros

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

Page 14: Writing Macros

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 !

Page 15: Writing Macros

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.

Page 16: Writing Macros

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!

Page 17: Writing Macros

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

Page 18: Writing Macros

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.

Page 19: Writing 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.

Page 20: Writing Macros

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

Page 21: Writing Macros

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

Page 22: Writing Macros

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

Page 23: Writing Macros

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

Page 24: Writing Macros

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 ….

Page 25: Writing Macros

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

Page 26: Writing Macros

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

Page 27: Writing Macros

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

Page 28: Writing Macros

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.