Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Clojure Macros

Writing Clojure Macros

A brief introduction to Clojure macros - what they are, how they work, and how to use them.

Kurt Christensen

October 14, 2011
Tweet

More Decks by Kurt Christensen

Other Decks in Programming

Transcript

  1. Who IS this guy?! Kurt Christensen! Programmer! Software development coach!

    for small teams, big corporations,! and everything in between! Available for purchase at:! [email protected]! Twitter: @projectileboy!
  2. What got me started…! "...I think I can give a

    kind of argument that might be convincing. The source code of the Viaweb editor was probably about 20-25% macros. Macros are harder to write than ordinary Lisp functions, and it's considered to be bad style to use them when they're not necessary. So every macro in that code is there because it has to be. What that means is that at least 20-25% of the code in this program is doing things that you can't easily do in any other language. However skeptical the Blub programmer might be about my claims for the mysterious powers of Lisp, this ought to make him curious. We weren't writing this code for our own amusement. We were a tiny startup, programming as hard as we could in order to put technical barriers between us and our competitors.” Paul Graham, Beating the Averages
  3. What’s so special about Lisp, and macros?! Language enables us

    to define and combine! increasingly powerful abstractions:! Data abstractions! Functional abstractions! Syntactic abstractions! Macros enable syntactic abstractions! (you can define your own special forms)!
  4. So then what is Clojure?! Simply, Clojure is a dialect

    of Lisp! which runs on the JVM and the CLR! ⇒  Lisp we can actually use!
  5. Getting started with Clojure! If you just want to try,

    go to http://try-clojure.org! Or…! 1)  Check that your machine has Java! 2)  Download the latest Clojure from http://clojure.org! 3)  Open a REPL (Read-Eval-Print Loop):! > java -jar clojure.jar Clojure 1.2.0 user=> 4) At the REPL prompt, begin to play:! user=> (+ 2 2) 4
  6. What does Clojure look like?! We’ll experiment in the REPL…!

    user=> (defn hello [name] (prn (str "Hello, " name))) #'user/hello user=> (hello ”Twin Cities Code Camp") "Hello, Twin Cities Code Camp” nil Note that prn prints out a string as a side effect,! and returns nil. Clojure guides you towards! functional programming.!
  7. Clojure Data Structures! The usual suspects, with some nice syntactic

    sugar! for generating them…! user=> (def my-vector [1 2 3 4 5 ]) #'user/my-vector user=> (def my-list '(1 2 3 4)) #'user/my-list user=> (def my-set #{1 2 3 4 }) #'user/my-set user=> (def my-map {:x 10 :y 20 }) #'user/my-map
  8. Clojure Functions! As in other functional programming languages (C#, etc.)

    functions are first-class citizens in Clojure! user=> (def hello (fn [name] (prn (str "Hello, " name)))) #'user/hello user=> (def hello #(prn (str "Hello, " %))) #'user/hello user=> (defn greet [f & names] (apply f names)) #'user/greet user=> (greet hello "Kurt") "Hello, Kurt" nil
  9. Functional Programming! With first-class functions, sequences (lists, etc.)! can be

    manipulated in powerful ways...! user=> (sort '(77 22 33 11 99 88)) (11 22 33 77 88 99) user=> (map #(+ 1 %) '(1 2 3 4 5)) (2 3 4 5 6) user=> (filter #(> % 50) '(10 20 60 70)) (60 70) user=> (reduce + '(1 2 3 4 5)) 15
  10. …and much, much more!! •  Seamless interoperability with Java! • 

    The sequence abstraction! •  Concurrency constructs! •  Software transactional memory! •  Multimethods! …but we’re here to talk about macros!!
  11. How do macros work?! In Lisp, everything is an expression

    ! - including functions – ! which gets evaluated at runtime! EXCEPT…! Macros are evaluated at compile time,! returning expressions which are ! in turn evaluated at runtime!
  12. How do macros work?! Isn’t this the same as a

    C/C++ macro?! No.! C/C++ macros perform string substitution,! Lisp macros are functions evaluated at compile-time! So?! Lisp macros have:! (a)  Lexical scope! (b)  Logic!
  13. How do macros work?! Other languages have meta-programming ! and

    code generation facilities! But only in Lisp are functions themselves ! also Lisp data structures! Lisp programs are their own abstract syntax trees! ⇒  We can use Lisp to make Lisp!
  14. The Simplest Macro! user=> (defmacro rev [x y z] (list

    z y x)) #'user/rev user=> (rev 2 1 +) 3
  15. What just happened?! We can use macroexpand to see !

    the code generated by the macro! user=> (macroexpand-1 '(rev 2 1 +)) (+ 1 2) user=> (macroexpand '(rev 2 1 +)) (+ 1 2) macroexpand recursively expands all macros! Normally you only want to see what your macro is doing; for that you call macroexpand-1!
  16. Gee, it sure would be nice if we had! some

    sort of templating language…! user=> (def x '(1 2 3)) #'user/x user=> `(0 x 4) ;Backquote shuts off evaluation (0 user/x 4) user=> `(0 ~x 4) ;Tilde evaluates and inserts (0 (1 2 3) 4) user=> `(0 ~@x 4) ;Tilde-at evaluates and splices (0 1 2 3 4)
  17. The Simplest Macro, One More Time! user=> (defmacro rev [x

    y z] `(~z ~y ~x)) #'user/rev user=> (rev 2 1 +) 3 Note to Lispers: Clojure does not have read macros!!
  18. Why do I care?! “I’ve never needed macros!”! …are you

    sure about that?! “Programming languages teach you not to want what they cannot provide. You have to think in a language to write programs in it, and it’s hard to want something you can’t describe.” Paul Graham, ANSI Common Lisp
  19. Look familiar?! How do we deal with expensive log messages?!

    user=> (def log-level :debug) #'user/log-level user=> (defn log [msg] (if (= log-level :debug) (prn msg))) #'user/log user=> (defn make-expensive-msg [] (prn "Generating expensive log message”) "Expensive log message") #'user/make-expensive-msg user=> (def log-level :prod) #'user/log-level user=> (log (make-expensive-msg)) "Generating expensive log message” nil
  20. Macros to the rescue!! user=> (defmacro log [msg] `(if (=

    log-level :debug) (prn ~msg))) #'user/log user=> (log (make-expensive-msg)) nil user=> (def log-level :debug) #'user/log-level user=> (log (make-expensive-msg)) "Generating expensive log message" "Expensive log message" nil
  21. The problem of variable capture! Compile-time sets different traps than

    run-time…! user=> (defmacro rep [n & body] `(dotimes [i ~n] ~@body)) #'user/rep user=> (let [i 100] (rep 3 (prn i))) java.lang.Exception: Can't let qualified name: user/i (NO_SOURCE_FILE:2) …WTF?!?! user=> (macroexpand-1 '(rep 3 (prn i))) (clojure.core/dotimes [user/i 3] (prn i)) Oh…!
  22. The solution: Gensyms! (that is, generated symbols)! Essentially what we

    want is for the system! to generate a symbol for us that is guaranteed! not to conflict with any other symbols!
  23. Clojure auto-gensyms! Clojure has a nifty little syntax for doing

    this:! user=> (defmacro rep [n & body] `(dotimes [i# ~n] ~@body)) #'user/rep user=> (let [i 100] (rep 3 (prn i))) 100 100 100 nil user=> (macroexpand-1 '(rep 3 (prn i))) (clojure.core/dotimes [i__9__auto__ 3] (prn i))
  24. Icky? Perhaps, but…! Just as you learn to deal with

    certain types! of runtime problems, with macros you must learn! to deal with certain types of compile time problems! (see chapter 10 of On Lisp)!
  25. Some languages try to avoid all of this! with the

    notion of hygienic macros,! which prevent unwanted variable capture! This is probably more trouble than it’s worth! More importantly, they also prevent ! wanted variable capture!
  26. Intentional variable capture! Macros which create variables that refer to

    their own internal state are called anaphoric macros! user=> (defmacro aif [pred & body] `(let [~'it ~pred] (if ~'it (do ~@body)))) #'user/aif user=> (macroexpand '(aif "Kurt" (prn it))) (let* [it "Kurt"] (clojure.core/if it (do (prn it)))) user=> (aif "Kurt" (prn it)) "Kurt” nil user=> (aif nil (prn "Kurt")) nil
  27. Intentional variable capture! A bit of trivia for any Lispers

    in the house…! user=> `(0 x 4) (0 user/x 4) user=> `(0 ~'x 4) (0 x 4) Note that Clojure wants to express fully namespace- qualified symbols. That’s why we need the weird syntax to do anaphoric macros in Clojure. !
  28. A Note About the Clojure Community…! Community is important for

    languages! The Clojure community discourages anaphoric macros, and seems to fear macros in general! Good thing: The Clojure community does not carry the baggage of the Common Lisp community! Bad thing: The Clojure community does not carry the wisdom of the Common Lisp community!
  29. Applied Macros! Macros enable one to define language.! The possibilities

    are endless!! Common macro uses:! Defining new syntacic abstractions that can’t be encaspulated in functions due to special form behavior! Increasing performance by executing code! at compile-time instead of at run-time !
  30. Applied Macros! A (weak) example: Kurt tries to generate HTML!

    …and remember that macroexpand-1 is your friend!!
  31. "One of the things you'll discover as you learn more

    about macros is how much day-to-day coding in other languages consists of manually generating macroexpansions... When there are patterns in source code, the response should not be to enshrine them in a list of ‘best practices’, or to find an IDE that can generate them. Patterns in your code mean you're doing something wrong. You should write the macro that will generate them and call that instead.” Paul Graham, Arc Tutorial
  32. What do you value?! I value simplicity over flexibility! I

    value few parts as a manifestation of simplicity! I value a complex part which! eliminates a large number of other parts! You may value different things…!
  33. References! Suggestion: First learn Lisp, and then worry! about the

    specifics of Clojure! ANSI Common Lisp! Paul Graham! http://www.amazon.com/dp/0133708756! Practical Common Lisp! Peter Seibel! http://gigamonkeys.com/book!
  34. References! On Lisp! Paul Graham! http://www.paulgraham.com/onlisptext.html! Let Over Lambda! Doug

    Hoyte! http://letoverlambda.com! Structure and Interpretation of Computer Programs! Abelman and Sussman! http://mitpress.mit.edu/sicp!
  35. Tools! Many prefer emacs…! Or TextMate (on the Mac)! IntelliJ

    IDEA with La Clojure plugin! Comprehensive categorized listing at! http://www.clojure-toolbox.com! Clojure should work fine on .NET, but there’s no tooling –! anyone care to write a Visual Studio plugin…?!
  36. Questions?! The fonts used in this presentation were Chalkboard, Liberation

    Mono, and Gill Sans Light.! No characters were harmed during the making of this presentation.!