View
5
Download
0
Category
Preview:
Citation preview
The C preprocessor can be used to define macros in C, macros are defined using a #define directive.
For example:
#define plus3(x) x+3
This replaces the code “plus3(<exp>)” with the code “<exp> + 3“
Careful construction must be used:
y = plus3(x)*5; will result in:
y = x+3*5;
This is not exactly what we intended!
But:
#define plus3(x) (x+3)
will do the job.
Another example:
#define max(n,m) (n<m)?m:n
Will cause problems if n is the expression ++x, it will be incremented twice!
max(++x,y);
will result in:
(++x<y)? ++x : y;
#define max(n,m) { int x = n,y=m; (x<y)?y:x; }
Careful programming must be used with macros. As a general rule, use only expressions without side-effects in C macros or use lexically hygienic macros.
** Assuming x and y are not used elsewhere (the call max(5,x) will fail in this case).
The problem can be resolved** this way:
Mit-define -> Define
1. (define (foo x1 x2 … xn)
2. body)
3. =>
4. (define foo
5. (lambda (x1 x2 … xn)
6. body))
And -> If
1. (and e1 e2 e3)
2. =>
3. (if e1 (if e2 e3 #f) #f)
Macro expansion is a syntactical transformation of an expression into an equivalent expression (syntactic sugar). For instance, Let expressions in Scheme are actually macros:
(let ((<Var-1> <Head-1>) … (<Var-k> <Head-k>)) <expr-1> … <expr-m>)
<=>
((lambda (Var-1 … Var-k) <expr-1> … <expr-m>) <Head-1> …<Head-k>)
(or <exp_1> … <exp_k>)
Wrong 0:
(if <exp_1> #t [macro-expand (or <exp_2> … <exp_k>)])
Problem: if <exp_1> is true, the value returned must be <exp_1> and not #t. remember anything that is not #f is true.
Wrong 1:
(if <exp_1> <exp_1> [macro-expand (or <exp_2> … <exp_k>)])
Problem: <exp_1> is evaluated twice!
Wrong 2:
(let ((v <exp_1>)) (if v
v [macro-expand (or <exp_2> … <exp_k>)] )
Problem: If any <exp_i> contains a variable called v – then that variable scope is changed. For example:
(or #f (and v w) #f #f) => (let ((v #f))
(if v v
(let ((v (and v w))) ;; here is the error. This v should refer to v (if v
v ….))
Variable Hygiene
Variable hygiene can be crucial in more complicated expansions. Suppose we want to expand or/and expressions using if:
Correct:
( (lambda (val_1 rest) (if val_1 val_1 (rest)))
<exp_1> (lambda () [macro-expand (or <exp_2> … <exp_k>)]))
Now the variable val_1 does not override the scope of the original expression. This transformation is Hygienic! Note that although this is correct for the general case, there are special end cases. All end-cases must be treated.
Macro-expansion can be applied on a variety of built in scheme expressions. Using if and lambda, it is possible to expand let, let*, begin, and, or, and more. Using set! it is possible to also expand letrec as well. Note that the quote mechanism can in itself be macro-expanded using the standard list and pair operators.
Wrong 3:
Use gensym.
Why is it wrong? because gensym is a sloppy and ugly mechanism. If you can avoid it, do so.
Expanding the Language
Important advantage of macro-expansion - lets programmers expand the language using their own defined expressions.
For instance, we can add a FOR statement to the scheme language by simply adding macro- expansion to the front end of the compiler:
(for <index_var> <from-expr> <to-expr> <body-expr>)
is defined as follows:
1. Evaluate <from-expr> and <to-expr> - to initial and final values (respectively).
2. Set <index_var> to initial value
3. If <index_var> is greater than final value, return `done.
4. Evaluate <body-expr>
5. Increment <index_var> by 1.
Basic principles:
* Correctness * Hygiene (no gensym)
Code after expansion:
(let ((from <from-expr>)) (to <to-expr>) (body-thunk (lambda () <body-expr>))
(letrec ((<index-var> from) (loop
(lambda () (if (>= to <index-var>)
(begin (body-thunk) (set! <index-var> (add1 <index-var>)) (loop))
‘done)))) (loop)))
Exercise:
Expand if using only and, or and lambda.
(if <test> <then> <else>)
(let ((val-test <test>)) (or (and val-test <then>)
(and (not val-test) <else>)
Not good enough: No variable hygiene (val-test).
Correct:
(let ((then-thunk (lambda () <then>)) (else-thunk (lambda () <else>))) (val-test <test>))
(or (and val-test (then-thunk)) (and (not val-test) (else-thunk))))
Also correct:
(let ((then-thunk (lambda () <then>)) (else-thunk (lambda () <else>))) (val-test <test>))
((or (and val-test then-thunk)
else-thunk)))
Wrong:
Recommended