Macros
- Macros enable us to define new special forms within scheme.
- Macros perform code transformations.
- We describe how to take parts of a special form and construct a regular scheme expression from that which is then evaluated.
- A macro is an operation performed on the source code of a program before its evauation.
- In scheme, the
define-macro
special form is used to define a source code transformation - Ex:
1
2
3
4
5
6
7
| scm> (define-macro (twice expr)
(list 'begin expr expr)
)
twice
scm> (twice (print 2))
2
2
|
- Because twice is a macro, it evaluates the body of the macro with the expression that is provided BEFORE the expression is evaluated.
- The macro constructs a new expression
(begin (print 2) (print 2))
, which is then ultimately evauated.
- If twice was a normal procedure, then the argument,
(print 2)
, would’ve been evauated before the procedure twice was ever called.- Two would be only printed once.
- Macros “copy” the raw form (data) of the arguments, and assembles a new expression which is then ran.
- Evaluation procedure of a macro call expression:
- Evaluate the operator sub-expression, which evaluates to a macro
- Call the macro procedure on the operand expressions without evaluating them first
- Evaluate the expression returned from the macro procedure
- Anything that we create as a macro is automatically a new special form in scheme.
- This means that we can pass in combinations of inputs like any other special form that we have.
- Ex: A macro that checks if a certain
1
2
3
4
5
6
7
8
9
10
11
12
| (define-macro (check expr)
(list 'if expr
''passed
(list 'quote (list 'failed: expr)))
)
This generates the following expression
(if expr
'passed
(quote (list 'failed: expr))
)
|
For Macro
- We want to create a macro that would apply a function over a list and return a new list.
- This is very similar to the map function, and to do this, we first must define a map function itself.
1
2
3
4
5
6
7
8
9
| scm> (define (map fn vals)
(if (null? vals)
()
(cons (fn (car vals)) (map fn (cdr vals)))
)
)
map
scm> (map (lambda (x) (* x x)) '(2 3 4 5))
(4 9 16 25)
|
- What we have now is already very similar to the for loop we are trying to implement. we just need to conver this form into a macro.
1
2
3
| (define-macro (for sym vals expr)
(list 'map (list 'lambda (list sym) expr) vals)
)
|
- Keep in mind that when we create lists in a macro, do not be lazy and us the single quote. The single quote would actually make everything within the list a string, which is not what we want. sometimes, we just want the value itself, not the value itself quoted.
Trace Macro
- We want to create a procedure that enables us to track recursive calls to a function, similar to the trace decorator in the python ucb module.
- We may achieve similar functionality using regular procedures without macros:
1
2
3
| (define fact (lambda (n)
(if (zero? n) 1 (* n (fact (- n 1)))))
)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| (begin
(define original fact) ; save our original function
(define fact
(lambda (n) (print (list 'fact n)) (original n))
) ; make our new function trace
(define result (fact 5)) ; Bind our function result to result
(define fact original) ; restore our original function
result ; return our result
)
|
- However, unlike python, this implementation does not have a separation of concerns between the actual tracing and the function itself. We want to generalize this procedure.
- As always, to create this macro, we try to construct a list that contains our four previous statements.
(define-macro (trace expr) (define operator (car expr)) ; isolate our original operator `(begin (define original ,operator) ; save our original procedure (define ,operator (lambda (n) (print (list (quote ,operator) n)) (original n))) ; redefine our procedure to trace itself. We must quote the value of the operator as otherwise we’d be getting the value of the operator instead. (define result ,expr) (define ,operator original) result ) )