✓ Macro Programming¶
S-expression¶
S-expressions¶
The s-expressions are strings that represent lists.
Some examples:
()
(1 2 3)
(1 (2 3))
The parenthesis marks the boundaries of the lists, and the elements of a list is either
an atom
or another list
The s-expression language¶
Here is the grammar of the s-expression.
s-expr : (element)*
element : atom
| s-expr
Atoms can be any of the scalar values:
Numbers
Strings and characters
Symbols
Extension of s-expression in Clojure¶
Clojure extends the basic s-expression by a few more constructs:
Set:
#{ ... }
Vector:
[ ... ]
Map:
{ ... }
Universal Expressiveness of s-expressions¶
Representation with s-expressions¶
S-expressions can be used to expresss just about anything. So, in a way, s-expression is a core language that can be used to represent any other language.
Programming¶
def factorial(n):
if n <= 1:
return n
else:
return n * factorial(n-1)
print factorial(100)
(my-program
(def factorial (n)
(if (n <= 1)
(return n)
(return (n * (call factorial (n - 1))))))
(call factorial 100))
Markup language¶
<Person id="1234">
<name>Albert Einstein</name>
<profession>Physicist</profession>
</Persion>
(person
(:id "1234")
(:name "Albert Einstein")
(:profession "Physicist"))
Stylesheet in CSS¶
body: {
font-family: Helvetica;
margin: 10px 0;
background: transparent;
}
;; This encoding requires that
;; the second element of (body ...) list
;; be a list containing even number of atoms.
(body (font-family "Helvetica"
margin (10 0)
background :transparent))
Clojure programming¶
Clojure source code is already in s-expression form.
Macro Programming¶
The reader¶
A reader is a program that transforms the native syntax to a s-expression.
Keep in mind that the \(\mathrm{SExpr}\) is to be interpreted by a Lisp runtime environment as a Turing-complete programming environment (such as Clojure).
Magic of Clojure¶
Clojure is homoiconic, so the source code is already in s-expression.
Using Clojure s-expression, we can express arbitrary data transformation.
What if clojure s-expression is the input to itself?
Macro expansion¶
Macros in Clojure¶
Macros are functions¶
(defmacro <function-name>
[ <arguments>... ]
<body>)
The arguments are s-expression before.
The return value must be a valid s-expression after.
Walking through¶
(evaluate 1 + 2)
is evaluated as during the macro expansion stage.
The body is evaluated with the scope:
x => 1
op => '+
y => 2
So, the body evaluates to
(+ 1 2)
Example¶
(defmacro evaluate2 [expr]
(list 'do
(list 'println (str expr))
(list 'println "=>" expr)))
#'user/evaluate2
(evaluate2 (+ 1 2 3))
(+ 1 2 3)
=> 6
nil
Walk through¶
(evaluate2 (+ 1 2 3)
creates the following binding during the macro-expansion.
expr => '(+ 1 2 3)
So,
(str expr) => "(+ 1 2 3)"
The returned value is:
(do (println "(+ 1 2 3)")
(println "=>" (+ 1 2 3)))
Remarks¶
In the macro of
(defmacro evaluate2 [expr]
(list 'do
(list 'println (str expr))
(list 'println "=>" expr)))
We need general computation:
(str expr)
to compute the string representation of expr.
We also need code generation:
'do
(list ...)
Language features for macro programming¶
Generating code is painful¶
(defmacro inc-10 []
(list 'let (vector 'x 10) (list clojure.core/inc 'x)))
#'user/inc-10
(macroexpand-1 '(inc-10))
(let [x 10] (#function[clojure.core/inc] x))
(inc-10)
11
Code template¶
Code template is a piece of valid clojure code that is not evaluated by the evaluator.
We can suppress evaluation using the backtick symbol.
All s-expressions in the template are pure data (with exception).
All symbols are resolved into their fully qualified form.
`(let [x 10] (inc x))
(clojure.core/let [user/x 10] (clojure.core/inc user/x))
This allows easy implementation of macros, but more on this later.
Single substitution in code template¶
We can exit from a template and evaluate part of the code inside the template.
`(let [x 10
y ~(rand-int 1000)]
(println ~(rand-int 1000) x y))
(clojure.core/let [user/x 10 user/y 827] (clojure.core/println 691 user/x user/y))
Slice substitution in code template¶
We can compute a list and insert it into a code template.
`(let [x 10
y ~(rand-int 1000)]
(println ~@(take 3 (repeatedly #(rand-int 1000))) x y))
(clojure.core/let [user/x 10 user/y 414] (clojure.core/println 75 709 326 user/x user/y))
Symbol generation¶
Code templates generates invalid variable names in the let
-form. To fix this, Clojure has a special syntax
that denotes new symbols to be inserted into the generated code.
`(let [x# 10
y# ~(rand-int 1000)]
(println ~@(take 3 (repeatedly #(rand-int 1000))) x# y#))
(clojure.core/let [x__5428__auto__ 10 y__5429__auto__ 367] (clojure.core/println 590 319 374 x__5428__auto__ y__5429__auto__))
Examples of macros¶
Echo¶
(defmacro echo [expr]
(let [message (str expr "=>")]
`(println ~message ~expr)))
#'user/echo
(echo (+ 1 2))
(+ 1 2)=> 3
nil
Timeit¶
(defmacro timeit [expr]
`(let [start# (System/currentTimeMillis)
result# ~expr
duration# (- (System/currentTimeMillis) start#)]
(println ~(str expr)
"=>"
result#)
(println "Took" duration# "ms")))
#'user/timeit
(timeit (apply + (range 1E7)))
(apply + (range 1.0E7)) => 49999995000000
Took 225 ms
nil
Infix operator¶
(defmacro infix [expr]
(let [[a op b] expr]
`(~op ~a ~b)))
#'user/infix
(infix (1 + 2))
3