![]() |
![]() |
|
home—lectures—exams—hws—breeze (snow day)
Q's, for future semesters: - define-struct, vs just struct: (struct (ship x y dx dy)) ; Cf. `define-struct` (ship 4 7 23 80) ; Cf. `make-ship` (ship-x ...) ; (same) - asteroids: physics/geom of acceleration, rotation, facing too distracting? - project: use racket imperative scanner, vs reading in entire input as sexpr? test harness too abstract?
Macros are code which generates other code (in the same source-language).
C has a primitive system, based purely on modifying strings. (It doesn't let you work on the level of syntax trees at all.) Before compiling a C program, the "c pre-processor" (cpp) makes a first pass where it does some string-substitution. (Use 'gcc -E' to show the results of pre-processing only.)
1 #include <stdio.h> 2 3 #define PI 2.14159+1 4 #define RADIANS_PER_DEGREE 2*PI/360 5 #define MAX(a,b) a >= b ? a : b 6 #define swap(x,y) {int tmp; tmp = x; x = y; y = tmp;} 7 8 int main() { 9 double p = 360 * RADIANS_PER_DEGREE; 10 printf( "The max is: %g.\n", 1/MAX(p++,PI) ); 11 12 13 const int ETM = 15; // #business days between "every third Monday" 14 //const int EOF = 10; // days in the pay period: "Every Other Friday". 15 // This previous line doesn't compile: 16 // error: expected identifier or '(' before '-' token 17 // Because the pre-processor is oblivious to C, we get weird error messages. 18 19 20 21 int a = 5, b = 99; 22 printf( "Before swap: a=%d, b=%d.\n", a, b ); 23 swap(a,b); 24 printf( "After swap: a=%d, b=%d.\n", a, b ); 25 26 // Happily: swap(++a,b) gives a syntax-error. 27 28 // Uh-oh! 29 int tmp = 5; 30 b = 99; 31 printf( "Before swap: tmp=%d, b=%d.\n", a, b ); 32 swap(tmp,b); 33 printf( "After swap: tmp=%d, b=%d.\n", a, b ); 34 } |
Note that one relatively-more-robust use of the preprocessor is conditional compilation:
#define USING_WINDOWS_VISTA #ifdef (USING_WINDOWS_XP) // code which *only* gets compiled on some platforms #define MAX_DISK_SIZE 300 void writeErrorLog( string msg ) { /* XP-specific code */ } #else void writeErrorLog( string msg ) { /* (non-XP)-specific code */ } #endif |
#if VERBOSE >=2 print("trace message"); #endif |
Overall, C's preprocessor is a failed experiment:
rather than a tool which does text-substitution (and knows
nothing about the language -- scoping, variables, etc.),
it's better to have features like
named constants,
module systems which search a library for declarations
but don't actually include the code,
and real functions
(which you can suggest that the compiler
Further abilities (and other sorts of pitfalls) are mentioned on wikipedia.
Possible reasons to want to write a macro:
You want to write a function, but one which short-circuits:
(define (implies a b) (or (not a) b)) |
We want:
(define-syntax-rule (implies a b) (or (not a) b)) |
; The function 'add3': (λ1 + 3 ____) ; Extract all the positive numbers from 'nums': (filter (λ1 > ____ 0) nums) |
How well does this C pre-processor macro work?
#define swap(a,b) int tmp = a; a = b; b = tmp; |
What does
if (n>0) swap(n,m) |
How can we fix it?
Wrapping the body in curly-brackets seems to help, but it doesn't really:
If you add curly brackets to
if (n>0) swap(n,m); else ++n; |
The standard hack is to remember to wrap all your macros in
(Examples from
15 subtle flaws in C/C++ macros).
Also, observe how the interaction between semicolon in the macro vs the macro call is all a bit haphazard and error-prone. Another thing stemming from the fact that we're doing macros based at the string-level, rather than at the syntax-tree level.
The real nail in the coffin:
What if somebody else already was using a variable
named '
We say this macro is non-hygienic,
because the introduced-variable '
(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, early BASIC, early FORTRAN, …) means it's hard to write large programs which don't use conflicting variable names. It's a giant step backwards to add global namespaces.)
void swap( int& a, int& b ) { // a,b are actually references to the real variables. int tmp = a; a = b; b = tmp; } |
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); |
create or replace procedure pr_Demo( n in number(10), ans out varchar(2) ) is begin out := 'HI' end; |
(define-syntax-rule (swap a b) (let* {[tmp a]} (begin (set! a b) (set! b tmp)))) (define tmp 5) (define b 99) (swap tmp b) b tmp |
It turns there is also a variant,
(define-syntax my-let (syntax-rules () ( (my-let ((id expr) ...) body) ; before ((lambda (id ...) body) expr ...) ; after ))) |
(my-let {[a 3] [id1 5] [c 7] } (+ a id1 c)) ; => ((lambda (a id1 c) (+ a id1 c)) 3 5 7) ; => 15 |
Challenge:
write
(define-and-pass-by-reference (swapper a b) …) |
Some personal favorite macros:
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) |
(filter (l1 > x n) data) |
But macros can be used for more than programmers:
it can be used to introduce entire scripting languages,
inside (say) racket!
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 racket.
For example,
here's a canonical parity-checker FSM,
which checks that the input contains
an even number of
BEGIN stoplight start state is s00 from s00 on input a go to s10 from s00 on input b go to s01 from s01 on input a go to s11 from s01 on input b go to s00 from s10 on input a go to s00 from s10 on input b go to s11 from s11 on input a go to s01 from s11 on input b go to s10 |
(Okay, you might decide to make a less verbose language, like
with lines like “
(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 |
Regardless of your input format, the first thing for you (as a macro-writer) to figure out is what code should this macro generate?
You can see what a racket program would do:
have a variable for the current-state,
and then (depending on that state and
(define parity (letrec {[s00 (lambda (input) (if (empty? input) #t (case (first input) [(a) (s10 (rest input))] [(b) (s01 (rest input))] [else false])))} ; No listed transition: fail. [s01 (lambda (input) (if (empty? input) #f (case (first input) [(a) (s11 (rest input))] [(b) (s00 (rest input))] [else false])))} ; No listed transition: fail. [s10 (lambda (input) (if (empty? input) #f (case (first input) [(a) (s00 (rest input))] [(b) (s11 (rest input))] [else false])))} ; No listed transition: fail. [s11 (lambda (input) (if (empty? input) #f (case (first input) [(a) (s01 (rest input))] [(b) (s10 (rest input))] [else false])))} ; No listed transition: fail. } s00)) |
(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)])) |
Note that we can take a non-parenthesized language, and implement it in racket with a two-to-three stage process:
Dynamic scope:(Lisp; Perl.)
class Foo() { int x = 3; void foo() { ++x; // If dynamically scoped: refers to 'most recently declared' x print(x); // in a *dynamic* sense! } void haha() { foo(); } void lala() { int x = 9; foo(); } void gaga() { int x = 5; foo(); } } class Hoo() { int x; Foo.foo(); } |
1
Actually, I use the variable name
(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—lectures—exams—hws—breeze (snow day)
©2011, Ian Barland, Radford University Last modified 2011.Dec.09 (Fri) |
Please mail any suggestions (incl. typos, broken links) to ibarland ![]() |
![]() |