In your opinion, is it appropriate to use a macro to abstract away repetitive boiler-plate code? Or is this better done in a procedure?
This is almost always a procedural abstraction rather than syntax, especially for beginners with Lisp... better to spend a lot of time with just procedural abstraction, higher-order functions, etc.
Syntactical abstraction I use for controlling the order of evaluation and sometimes to put a pretty syntax around use of lambda.
The begin1 is an example of control.
An example of the latter use of pretty syntax... consider a scenario where you are using a resource and you want some "before" and "after" actions. The base level way to implement this is with higher-order procedures. But who wants to write (lambda () ...) all the time? So on top of this build some pretty syntax. For example...
(define (call-when-ready procedure) (wait-until-ready time-out) (if (not (ready?)) (call-when-ready procedure)))
Use it like this...
(call-when-ready (lambda () (display "I am glad this is finally ready!") (newline) (do-something)))
This is fine when someone else is generating the code for you. Normally you might want to abstract the procedure as a sequence of statements...
(when-ready (display "I am glad this is finally ready!") (newline) (do-something))
And so when-ready is defined as a macro that calls call-when-ready...
(define-macro (when-ready . body) `(call-when-ready (lambda () ,@body)))
-Patrick
Afficher les réponses par date
On 8/18/05, Logan, Patrick D patrick.d.logan@intel.com wrote:
In your opinion, is it appropriate to use a macro to abstract away repetitive boiler-plate code? Or is this better done in a procedure?
Two more places where a simple procedure won't do, but you have boilerplate code which calls for a macro are (both have occurred in my recent coding work on n-body integrators):
1. Various repetitive binding constructs i.e.:
(let ((m (body-m b)) (q (body-q b)) (p (body-p b)) (t (body-t b))) 'do-something)
becomes
(with-body (b) 'do-something)
This type of macro cannot be written with the syntax-rules defined in R5RS (there's no way to introduce the literal identifier 'm into the expansion of the macro), but can be written with the syntax-case extension (which is available in gambit), though it's easiest with define-macro:
(define-macro (with-body body . exprs) (let ((body-sim (car body))) `(let ((m (body-m ,body-sym)) (q (body-q ,body-sym)) (p (body-p ,body-sym)) (t (body-t ,body-sym))) ,@exprs)))
A fun exercise: convert this macro to (with-bodies (b1 b2 ...) ...) which does the above if called like (with-bodies (b) ...), but does
(let ((m1 (body-m b-one)) (m2 (body-m b-two)) (m3 (body-m b-three)) ...) 'do-something)
if called like (with-bodies (b-one b-two b-three ...) 'do-something).
2. Making little sub-languages for specialized processing e.g.
(with-vectors (v1 v2 v3) (v1 <- (+ v2 v3)))
for summing up the vectors v2 and v3 and storing it in v1. (I'm not going to put this macro up because it's long---the code is buried in this post: http://wmfarr.blogspot.com/2005/06/bigloo-macros-and-repl.html .)
#1 involves introducing new bindings into a section of code, and #2 involves changing the evaluation rules for a piece of code (the vector assignment is evaluated once for each index with the corresponding variables bound to elements of a vector). Procedures would not work for either of them; you have to have a macro.
My point is to illustrate just how nice it is to have macros in a language---and to encourage you to use macros to remove your boiler-plate code! These two macros have saved me literally thousands of lines of typing (and made my code clearer) over the past month, and they didn't even take very long to write.
Enjoy, Will