Macro fun!

For the Compleat Fan
Locked
rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Macro fun!

Post 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.
(λx. x x) (λx. x x)

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post 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

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post 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).
WBR, Dmi

Locked