home—info—lectures—exams—archive
lect14a
better macros
How well does this C pre-processor macro work?
#define swap(a,b) int tmp = a; a = b; b = tmp;
|
-
Well,
just to be safe, I'd add parentheses around each use of a
and b (in case the user calls something complicated like
swap(aStruct.aField, b[3+7]);
I'm not sure this is strictly needed, but I certainly
can't prove to myself that it's safe to omit them
(and I can prove to myself that it can't hurt to include parentheses them).
-
Note that I'm not worried about
swap(3,x) — this will cause a syntax error
after expanding the macro (“3 = tmp” is illegal),
but that's okay — calling swap(3,x) should
case an error.
(Giving clear error messages in terms of the original source code
is more difficult, though.)
-
C doesn't allow “++x = 3”.
But if some language did (heaven forbid!), then
swap(x++,++y) increment things twice.
-
This only works if you can declare tmp in the middle
of a block of code (C does allow this, but not all imperative
languages do).
-
The real nail in the coffin:
What if somebody else already was using the variable 'tmp'?
This version wouldn't compile.
Even if modified the macro to produce a nested-block,
it would compile, but give the wrong answers if you tried
swap(tmp,b).
We say this macro is non-hygienic,
because the introduced-variable 'tmp' can
conflict with existing variables.
(One of the most powerful things about regular functions is
that they have their own scope;
programming where all variables are global
(e.g. assembly, and early BASIC)
means it's hard to write large programs which don't use conflicting
variable names.
-
In C, we can (kinda) write swap as a true method,
using the & (address-of) operator
(to fake pass-by-reference).
void betterSwap( int* a, int* b ) {
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
// call this by:
int a = 5, b = 99;
betterSwap(&a,&b);
|
-
But in Scheme, there is no "address-of" operator,
we really do want this to be an inlined method,
except that we don't want tmp to conflict with
any other variable.
Scheme macros do that, with hygiene:
; Think:
; (define (swap a b)
; (let* {[tmp a]}
; (begin
; (set! a b)
; (set! b tmp))))
;
(define-syntax swap
(syntax-rules ()
((swap a b)
(let ((tmp a)) ; a hygienic 'tmp'
(set! a b)
(set! b tmp)))))
(define tmp 5)
(define b 99)
(swap tmp b)
b
tmp
|
define-syntax means
“run this on the syntax-tree, before
calling eval!”
That's what a macro is.
It turns out this mysterious syntax-rules
allows pattern-matching (just like prolog!).
Here's an example,
based on
this page;
it re-writes let as lambda,
as we discussed earlier:
(define-syntax my-let
(syntax-rules ()
( (my-let ((id expr) ...) body) ; before
((lambda (id ...) body) expr ...) ; after
)))
|
After doing this,
(my-let {[a 3]
[id1 5]
[c 7]
}
(+ a id1 c))
; =>
((lambda (a id1 c) (+ a id1 c)) 3 5 7)
; =>
15
|
Some personal favorite macros:
-
assert* (making a bunch of asserts all at once),
-
define/opt (combines define
with lambda/opt),
-
define/case (combines define
with case-lambda),
-
letdef* (combines let* with define)
-
when-not (an improved name for unless —
probably human-interace to introduce redundant names)
-
begin1 (like begin0, except that
it returns the 2nd value from a list-of-expressions)
- test (has been obviated by check-expect)
A personal favorite macro:
I often find myself writing functions of one argument, like
; Suppose I have a list 'data' and a threshold 'n';
; grab all the elements of 'data' bigger than 'n':
(filter (lambda (x) (> x n)) data)
|
I do things like this so often, I'd like to have my
own abbreviated version for creating a function:
Note that x is an introduced variable.
(We want it to shadow any existing1 variable name!)
The code for this is actually pretty involved,
using scheme's hygienic macro system
to introduce non-hygienic macros.
Therefore, the code is given only as an un-explained footnote2
Little Languages
But macros can be used for more than programmers:
it can be used to introduce entire scripting languages,
inside (say) Scheme!
Here is an example -- taken from
Krishnamurthi's manifesto
Swine Before Perl:
Suppose we want to implement finite state machines.
While we could
have our non-programming expert friends
learn our own programming language plus our own libraries,
or we could write a program which takes in strings and interprets them,
there is a third approach:
let them write something that looks like a FSM spec,
but is translated directly into Scheme.
For example,
here's a boring traffic-light automoton
(define parity
(automaton s00
(s00 T : (a -> s10)
(b -> s01))
(s01 F : (a -> s11)
(b -> s00))
(s10 F : (a -> s00)
(b -> s11))
(s11 F : (a -> s01)
(b -> s10))))
(parity '(a a b a b a)) ; yields true
(parity '(a a b a b a b)) ; yields false
|
You can see what a scheme program would do:
have a variable for the current-state,
and then (depending on that state and (first input))
recur with a new value for the current state.
Actually, it might even make four functions,
s00,
s01,
s10,
s11;
for example
s10 be a function,
which given
a list starting with
a will (tail-)recur on s00,
and given
a list starting with
b it will (tail-)recur on s11.
(define-syntax automaton
(syntax-rules (-> :)
[(automaton init-state
(state accepting? : (cndn -> new-state)
...)
...)
(letrec ([state (lambda (input)
(if (empty? input)
(symbol=? accepting? 'T)
(case (first input)
[(cndn) (new-state (rest input))]
...
[else false])))] ; No listed transition: fail.
...)
init-state)]))
|
1
Actually, I use the variable name __ instead of x,
reminiscent of "fill in the blank (with the argument)":
(filter (l1 > __ n) data)
↩
2
(define-syntax (l1 stx)
(syntax-case stx ()
[(src-l1 proc args ...)
; This nested syntax-case is so that __ has the same
; context/scope as proc,args. (That is, we
; don't want to introduce a hygienic __ that is really
; different from any __ occuring in proc,args.)
; So create a syntax-object __ that takes context from src-l1,
; and then return a lambda which binds that __.
;
(syntax-case (datum->syntax #'src-l1 '__) ()
[__ (syntax/loc #'src-l1 (lambda (__) (proc args ...)))])]))
; There might be a cleaner way of writing the above
|
↩
home—info—lectures—exams—archive