Macro fun!
Posted: Fri May 05, 2006 6:59 pm
Don't you just love syntactic sugar? I don't know about you but I like it so much, I've already rotted out my syntactic teeth. (* rimshot *)
Here's a case for your consideration. Recently, I wanted to write a macro which looked like 'dolist' but behaved like 'map'. I decided to call it 'collect', and I wanted its usage to be such that I could do the following, for instance.
Then I thought, wouldn't it be nice for the macro definition to expand in the manner that backquote does in Common Lisp? The most important facility being the list splicing by way of the ',@' syntax. That is, I'd like for the definition of 'collect' to have this as the punchline:
Then I could nix all the code I had to write, at every instance, to do the work of the splicing of 'body' and 'lists' into the 'map' expression.
The following definition is as close as I could get.
where 'comma-expand' is like newLISP's 'expand' but instead of taking symbol arguments, it just scans the given expression for symbols preceded by ',' and ',@' and expands them accordingly.
My definition of 'comma-expand' is:
Personally, I like the clarity of the definition of 'collect' (i.e. with the backquote syntax). However, the only problem I've had is that 'collect' is now sort of slow, due to the call overhead of the "housekeeping" code. Oh well. Maybe if Lutz likes it we will get the present of an intrinsic (read "faster") backquoting mechanism. I almost hate to mention it though, since it seems that lately, every time I write something to the forum, it ends up being a request for Lutz to put something on his todo list. Sorry Lutz.
Here's a case for your consideration. Recently, I wanted to write a macro which looked like 'dolist' but behaved like 'map'. I decided to call it 'collect', and I wanted its usage to be such that I could do the following, for instance.
Code: Select all
> (collect (x '(1 2 3) y '(4 5 6)) (list y x))
((4 1) (5 2) (6 3))
> (collect (x '(1 2 3) y '(4 5 6)) (println y) x)
4
5
6
(1 2 3)
Code: Select all
`(map (fn ,vars ,@body) ,@lists)
The following definition is as close as I could get.
Code: Select all
(define-macro (collect)
(letn ((parms (args 0))
(plen (length parms))
(vars (list-nth (sequence 0 (- plen 1) 2) parms))
(lists (list-nth (sequence 1 (- plen 1) 2) parms))
(body (1 (args))))
(comma-expand (map (fn ,vars ,@body) ,@lists))))
; where 'list-nth' is defined as:
(define (list-nth indices lisst)
(map (fn (n) (nth n lisst)) indices))
My definition of 'comma-expand' is:
Code: Select all
(define-macro (comma-expand form)
(catch
(cond ((quote? form)
(comma-expand-func (eval form) '()))
((list? form)
(eval (comma-expand-func form '())))
(true form))))
(define (comma-expand-func form acc)
(cond
((not (list? form)) form)
((empty? form) (reverse acc))
((lambda? form)
(let ((fn-tail (map (fn (x) x) form))) ; dirty trick.
(append (lambda) (comma-expand-func fn-tail '()))))
((quote? (form 0))
(comma-expand-func
(1 form)
(cons (append '(quote)
(list (comma-expand-func (eval (form 0)) '())))
acc)))
((list? (form 0))
(comma-expand-func (1 form)
(cons (comma-expand-func (form 0) '())
acc)))
((= ', (form 0))
(if (not (symbol? (form 1))) (throw 'CAN-ONLY-EXPAND-SYMBOLS))
(let ((sym-name (name (form 1))))
(if (= "@" (sym-name 0)) ; this means splice is required.
(letn ((var (symbol (1 sym-name)))
(val (eval var)))
(if (not (list? val)) (throw 'CAN-ONLY-SPLICE-LISTS))
(comma-expand-func (2 form) (append (reverse val)
acc)))
(comma-expand-func (2 form) (cons (eval (form 1)) acc)))))
(true
(comma-expand-func (1 form) (cons (form 0) acc)))))