Macro fun!

Started by rickyboy, May 05, 2006, 11:59:40 AM

Previous topic - Next topic

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.


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


`(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.


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


(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

#1
Quote
... 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):



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


Lutz

Dmi

#2
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
(filter (fn (x) (starts-with x prefifx))
        (map string
             (unique (filter symbol? (flat lst)))))

to automatically form args list for (expand).
WBR, Dmi