✓ 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

    1. an atom

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

\[ \mathrm{reader}: \mathrm{Syntax} \to \mathrm{SExpr} \]

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

\[ \mathrm{Syntax} \overset{reader}{\longrightarrow} \underbrace{\mathrm{SExpr}}_{\mathrm{before}} \overset{macro}{\longrightarrow} \underbrace{\mathrm{SExpr}}_{\mathrm{after}} \]

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.

Example

(defmacro evaluate [x op y]
    (list op x y))
#'user/evaluate
(evaluate 1 + 2)
3

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.

  1. All s-expressions in the template are pure data (with exception).

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