Page 1 of 1

Macro fun!

Posted: Fri May 05, 2006 6:59 pm
by rickyboy
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.

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)
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:

Code: Select all

`(map (fn ,vars ,@body) ,@lists)
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.

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))
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:

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)))))
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.

Posted: Fri May 05, 2006 11:50 pm
by Lutz
... it ends up being a request for Lutz to put something on his todo list.
no backquote in newLISP ;), personally I never could warm up to the backquote notation, in my feeling it looks weird and cryptic, but of course that is a very subjective view point. I just think it doesn't belong into newLISP.

but here is another idea, wihtout map, for the 'pairifying' mechanism you are showing with collect (unrelated to the backquote discussion):

Code: Select all

> (transpose (list '(1 2 3) '(4 5 6)))
((1 4) (2 5) (3 6))
> 
Lutz

Posted: Sat May 06, 2006 9:40 pm
by Dmi
Hi, Rickyboy, Hi, Lutz!

Imho, having an expand-like function (let's say, expand-plain), that will expand elements by ",@"-rule (having in mind that (expand) working like ","-rule) will completely solve the problem. So it can then be possible to write nice macro-constructs with (expand) and (expand-plain).
Or, to write a complete wrapper that will use something like

Code: Select all

(filter (fn (x) (starts-with x prefifx)) 
        (map string
             (unique (filter symbol? (flat lst)))))
to automatically form args list for (expand).