(def square (fn [x] (* x x)))
I never think about myself as an artist working in this time. I think about it in macro.
The "real" way to define a function:
(def square (fn [x] (* x x)))
Special syntax:
(defn square [x] (* x x))
Syntactic sugar to remove boilerplate
Macros extend the syntax of Clojure
(when (< x 2) (println "It's less than 2!") :ok)
Expands to
(if (< x 2) (do (println "It's less than 2!") :ok))
(macroexpand-1 '(when (< x 2) (println "It's less than 2!") :ok)) => (if (< x 2) (do (println "It's less than 2!") :ok))
(macroexpand-1 '(defn square [x] (* x x))) => (def square (clojure.core/fn ([x] (* x x))))
Functions that manipulate code
Think of it as text manipulation
Passed input forms unevaluated
Replaces the form at compile time
Have a :macro
flag set in metadata
(map (fn maybe-ok [x] (when (< x 2) :ok)) (range 4)) => (:ok :ok nil nil)
when
is only expanded once
The form is expanded at compile time
maybe-ok
function is compiled to bytecode
Arguments manipulated at compile time
Arguments not evaluated
Cannot be replaced by a function
and => CompilerException: Can't take value of a macro
(map and [true false] [true true]) => CompilerException: Can't take value of a macro
Macros are not values
Cannot be passed to higher order functions
Less useful than functions
(map #(and %1 %2) [true false] [true true]) => (true false)
Cannot apply arguments though… |
#(every? identity %&)
(defmacro infix [[operand1 op operand2]] (list op operand1 operand2))
(infix (1 + 1)) => 2
(macroexpand '(infix (1 + 1))) => (+ 1 1)
(defmacro my-when [test & body] (list 'if test (cons 'do body)))
(macroexpand-1 '(my-when (< x 1) (println "hi") :ok)) => (if (< x 1) (do (println "hi") :ok))
test and body are values, if and do are symbols |
(defmacro zen1 [x] (println "x:" x) x) (zen1 (+ 1 2)) => x: (+ 1 2) 3
vs
(defn zen2 [x] (println "x:" x) x) (zen2 (+ 1 2)) => x: 3 3
`(inc 1) => (clojure.core/inc 1)
Special form called syntax-quote (also called back-quote)
All symbols in a syntax-quote form get fully qualified
`
vs '
`(1 2 ~(+ 1 2) ~@(map inc [3 4 5])) => (1 2 3 4 5 6)
Syntax | Name | Behavior |
---|---|---|
` | Back-quote | Fully qualified quote |
~ | Unquote | Insert a value |
~@ | Unquote-splicing | Insert a sequence |
(defmacro m1 [] '(inc 1))
(defmacro m2 [] `(inc 1))
(let [inc dec] {:m1 (m1) :m2 (m2)})
=> {:m1 0, :m2 2}
Symbols have contextual meaning
Macros are defined with no context
(defmacro bad [expr] (list 'let '[a 1] (list 'inc expr))) (bad 0) => 1 (def a 0) (bad a) => 2
The parameter name collided with the implementation |
(macroexpand-1 '(bad a)) => (let [a 1] (inc a))
(defmacro good [expr] `(let [a# 1] (inc ~expr))) (good a) => 1 (good 0) => 1
a#
expands to a randomly generated symbol
(macroexpand-1 '(good a)) => (clojure.core/let [a__6500__auto__ 1] (clojure.core/inc a))
When working on a non-trivial macro a good strategy is:
Step 1: Write a function!
Step 2: Call your function from the macro
Keep the macro small and offload form transformations to other functions |
Almost never
To provide new syntax: core.async
(def echo-chan (chan)) (go (println (<! echo-chan))) (>!! echo-chan "hello") => true hello
Functions that produce code
Manipulate code… as data
Homoiconic: the language text has the same structure as its abstract syntax tree
Code transformed using the same representation
Nested code is well represented as a data structure
Language can be extended conveniently
Lisp "syntax" underpins Clojure "syntax"
Macros are common in clojure.core and libraries
Macros can have surprising behavior
Debugging
Identifying macros
Expanding macros
Manipulate the operand forms
Do not evaluate the input forms
Not functions
Cannot be passed to other functions
See manual end of section 7
(defmacro ignore [expr] nil)
(defmacro when2 [test & body] (list 'if test (cons 'do body))
(defmacro spy [expr] `(let [result# ~expr] (println "Expression" '~expr "has value" result#) result#))
(macroexpand-1 '(spy (* 2 3))) => (clojure.core/let [result__6418__auto__ (* 2 3)] (clojure.core/println "Expression" (quote (* 2 3)) "has value" result__6418__auto__) result__6418__auto__)
(+ 1 (spy (* 2 3))) => Expression (* 2 3) has value 6 7
See clojure source code
(defmacro or2 ([] nil) ([x] x) ([x & next] `(let [or# ~x] (if or# or# (or ~@next)))))