[5/3/99] =============================================================================== * Macros can be used in a various ways, the most common usage is introducing ways to modify values: -------------------- (defmacro (push! val var) (list 'set! var (list 'cons val var))) (define s '()) (push! 1 s) (push! 2 s) s --> (2 1) -------------------- Another common usage is to introduce new syntax that is translated into some Scheme syntax, for example: -------------------- (defmacro (dolist var lst expr) (list 'for-each (list 'lambda (list var) expr) lst)) (dolist x '(1 2 3) (echo x)) -------------------- =============================================================================== * A more sophisticated "real-world" macro example: We know that Scheme uses lexical scoping, for example: -------------------- (define x 1) (define (get-x) x) (let ((x 2)) (get-x)) --> 1 -------------------- But in some cases it is nice to get dynamic binding - to rebind global variables temporarily. To have this feature we would have to modify the way that the Scheme evaluator works (which is very easy to implement with tiny-scheme) but another solution is to use a macro to add another "special-form" (macros can be thought of as ways to add special forms, except that they are extensions to the basic Scheme rather than a primitive special form). Define a "fluid-let" macro for this. We could do it using this transformation: (fluid-let VAR EXP BODY-EXP) ==> (let ((temp VAR)) (set! VAR EXP) (let ((result BODY-EXP)) (set! VAR temp) result)) This can be easily implemented: -------------------- (defmacro (fluid-let var exp body) `(let ((temp ,var)) (set! ,var ,exp) (let ((result ,body)) (set! ,var temp) result))) -------------------- Now we get: -------------------- (define x 1) (define (get-x) x) (fluid-let x 2 (get-x)) --> 2 (get-x) --> 1 -------------------- Some notes about the above macro definition: - It uses Schemes "quasi-auote" mechanism that allows easy definition of macros, it is just a shortcut for: (list 'let (list (list 'temp var)) ...) - We are still limited to functions of a fixed number of arguments, so we can't have a real let-like body. (If interested - see below) - A "real" fluid-let is also supposed to handle a list of bindings, this is simple to implement but would make it more complicated. - One possible feature, but also a problem we have is that since we use Swindle's set! form. We might have something like this: -------------------- (define x (list 1 2 3)) (define (get-car-x) (car x)) (fluid-let (car x) 11 (get-car-x)) --> 11 (get-car-x) --> 1 -------------------- but then we get strange behavior as in (try to see what the problem is): -------------------- x --> (1 2 3) (fluid-let (car x) 11 (get-x)) --> (1 2 3) -------------------- and also errors like this: -------------------- (fluid-let (car x) 11 (set! x 1)) --> ERROR -------------------- Yet another problem is that what we do only an imitation of dynamic binding, so this won't work if the variable that is being set! is different than the binding that we call sees, an example clarifies this: -------------------- (define x 3) (define (get-x) x) (let ((x 4)) (fluid-let x 5 (get-x))) --> 3 -------------------- =============================================================================== * A more severe problem is that our macro mechanism is not hygiene: it can disrespect lexical scoping rules, as the above form does: -------------------- (define x 1) (define temp 666) (fluid-let x temp (get-x)) --> 1 (fluid-let x 2 temp) --> 1 -------------------- both are errors! One dirty solution is to use a function such as "gensym" that always return different symbols (such as g1, g2, g3, ...). The problem is that if you do any kind of compile time macro expansion and saving the result in a file (as MzScheme actually does) then there is a possibility that you will load two files that use the same symbol and getting the same problem. [A solution to this in some Scheme dialects such as Emacs-Lisp is to have "special" symbols that behave as boxed values.] A much better solution is to a hygiene macro system. There is a description of one in the Scheme Revised Report, but it is too complicated to go over... =============================================================================== * A higher-order function that can come in handy at some times is "apply". It behaves in the following way: (apply f v) --> (f v1 v2...) where v evaluates to the list containing v1, v2, ... Two quick notes about it: 1. It almost looks like it is not really needed because it is possible to do the same without it, but that is not true. 2. There are many cases where it can be used in cute ways, such as: -------------------- (define (sum-list lst) (apply + lst)) (define (flatten lsts) (apply append lsts)) -------------------- Here is a quick usage example, that takes in a matrix represented as a list of lists and transposes it: -------------------- (define (transpose mtx) (apply map (cons list mtx))) -------------------- To really understand how this works - you have to know exactly how apply, map and list are working. Hard exercise: try to write a transpose function that works the same as this, except that it _doesn't_ allocate new cons cells but modifies the existing pointer structure (i.e, it doesn't use cons or other functions that create cons cells). Two possible solutions are to play with the contents of the matrix (the car parts) and another would be to play with the links (the cdr parts). ===============================================================================