Macros - Fri, Nov 8

This lecture will deal with the second extra credit problem on the Scheme project.

Programs as Data

A Scheme expression is a Scheme list. Scheme programs consist of expressions such as:

The built-in Scheme list data structure (a linked list) can represent combinations

scm> (list `quotient 10 2)
(quotient 10 2)

scm> (eval (list `quotient 10 2))
5

In such a language, it is straightforward to write a program that writes a program.

Let's look at a couple of examples of a program writing a program.

Example: Factorial

(define (fact n)
  (if (= n 0) 1 (* n (fact (- n 1)))))

(define (fact-exp n)
  (if (= n 0) 1 (list '* n (fact-exp (- n 1)))))

Calling (fact 5) yields 120. Calling (fact-exp 5) yields the expression for computing (fact 5): (* 5 (* 4 (* 3 (* 2 (* 1 1)))))

Example: Fibonacci

(define (fib n)
  (if (<= n 1) n (+ (fib (- n 2)) (fib (- n 1)))))

(define (fib-exp n)
  (if (<= n 1) n 
      (list '+ (fib-exp (- n 2)) (fib-exp (- n 1)))))

Same idea here. (fib-exp n) will yield the expression for computing (fib n) without actually computing (fib n).

Macros

A macro is an operation performed on the source code of a program before evaluation. They exist in many languages, but are easiest to correctly define in languages like Lisp where every expression is a list.

Scheme has a define-macro special form that defines a source code transformation.

(define-macro (twice expr)
              (list 'begin expr expr))

scm> (twice (print 2))			; this is equivalent to the following
scm> (begin (print 2) (print 2)); both of these print:
2
2

Evaluation procedure of a macro call expression:

  1. Evaluate the operator sub-expression, which evaluates to a macro
  2. Call the macro procedure on the operand expressions without evaluating them first
  3. Evaluate the expression returned from the macro procedure

Example: For Macro

Define a macro that evaluates an expression for each value in a sequence.

scm> (map (lambda (x) (* x x)) '(2 3 4 5))

(define-macro (for sym vals expr)
  (list 'map (list 'lambda (list sym) expr) vals)
)

scm> (for x '(2 3 4 5) (* x x))
(4 9 16 25)

This example just created a for form in Scheme. The power of defining macros is that it allows you to extend the programming language in new ways!

Quasi-Quotation

scm> '(a b c)
(a b c)

scm> `(a b c)
(a b c)

scm> (define b 3)
b

scm> `(a b c)
(a b c)

scm> `(a ,b c)
(a 3 c)

Quasi-quotes are the ` character, as opposed to quotes which are the ' character.

scm> (quasiquote (a (unquote b) c))
(a 3 c)

scm> `(a (,b) c)
(a (3) c)

scm> `(a ,(+ 2 b) c)
(a 5 c)

Use Case

This allows for some simplification. Consider the following code:

(define-macro (check expr)
              (list 'if expr ''pass
                    (list 'quote (list 'fail: expr))))

This code does the same thing as:

(define-macro (check expr)
              `(if ,expr 'pass '(fail: ,expr)))