RU beehive logo ITEC dept promo banner
ITEC 380
2009spring
ibarland

homeinfolecturesexamshwsarchive

lect11
macros; prolog intro


Macros

Macros are code which generates other code (in the same source-language).

Possible reasons to want to write a macro:

Note that macros in scheme are kind of nice: because S-expressions correspond to syntax trees, your macro can go ahead and work at the level of syntax.

The C pre-processor

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    // This next line doesn't compile, complaining about a hyphen (?!):
15    // const int EOF = 10;  // days in the pay period: "Every Other Friday".
16    // Because the pre-processor is oblivious to C, we get weird error messages.
17  
18  
19  
20    int a = 5, b = 99;
21    printf( "Before swap: a=%d, b=%d.\n", a, b );
22    swap(a,b);
23    printf( "After  swap: a=%d, b=%d.\n", a, b );
24  
25    // Happily: swap(++a,b) gives a syntax-error.
26  
27    // Uh-oh!
28    int tmp = 5;
29    b = 99;
30    printf( "Before swap: tmp=%d, b=%d.\n", a, b );
31    swap(tmp,b);
32    printf( "After  swap: tmp=%d, b=%d.\n", a, b );
33    }
(We talk about pitfalls of using the preprocessor.)

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
or (from Wikipedia)
#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 inline). Conditional compilation can be done by having built-in functions like "getCurrentOS" plus an aggressive compiler.

Further abilities (and other sorts of pitfalls) are mentioned on wikipedia.

Now that we have the need for 'smarter' macros,
here are some applications we might use for them:

  - I wish 'let*' allowed me define functions
    w/o explicilty typing 'lambda'.
  - l1
  - Little Languages: a macro language for FSMs


 - auto-generate getters, setters, public/private fields, constructor...
 - auto-generate the boring code in 'eval', 'equals', ...
   (Though, see scheme's partial solution in L3-soln.ss)
 - when using the delegate pattern


Reflection vs Macros:
 - Reflection is a way of 'writing code expands to other code'
 - less powerful in some ways: you can't make arb. macros,
   only produce method-calls to existing methods.
 - However, it's more powerful in some ways:
   you don't need to know field-names etc in advance.

better macros

How well does this C pre-processor macro work?

#define swap(a,b)   int tmp = a; a = b; b = tmp;

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

Challenge: write define-and-pass-by-reference, which is just like define except that all arguments are actually passed by references. That is,

(define-and-pass-by-reference (swapper a b)
   )
Have this macro turn around and generate code which

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)
I do things like this so often, I'd like to have my own abbreviated version for creating a function:
  (filter (l1 > x n) data)
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)]))

Prolog intro

We will refer to Notes from Dr.Okie on:

Also, a particularly nice Prolog tutorial is at learnPrologNow.org.


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
     

homeinfolecturesexamshwsarchive


©2009, Ian Barland, Radford University
Last modified 2009.Apr.24 (Fri)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme