macroscommon-lispdestructuringclisp

Can I have an indeterminate number of destructuring lists in a lisp macro lambda list?


I'm trying to write a macro that expands to an unspecified number of function calls, but I also want to be able to specify exactly one argument to be passed to each function in the macro call. I would basically want its syntax to look like a let call:

(let ((var1 1)
      (var2 2)
      ...
      (varn n))
  body)

but passing arguments to functions instead of binding variables, so a call like this

(foo ((fn1 arg1)
      (fn2 arg2)
      ...
      (fnn argn))
  body)

expands to this:

(progn
  (funcall fn1 arg1)
  (funcall fn2 arg2)
  ...
  (funcall fnn argn)
  body)

As far as I can tell, the list-destructuring behaviour of macro lambda lists allows me to pass an unspecified number of forms to the macro in a nested lambda list:

(defmacro foo ((&rest call-forms) &body body)
  ...)

OR define a rigid syntax for a form in a nested lambda list:

(defmacro foo ((single-fn single-arg) &body body)
  ...)

but NOT both:

(defmacro foo ((&rest (fn arg)) &body body) ; gibberish
  ...)

Is there a loophole or workaround I'm not seeing? I know it seems arbitrary, but the call syntax I specified above would be ideal for what I'm doing. I get that this might be out of the question, since let is a special operator and its behaviour appears to be unique, but I'd love to be proven wrong.


Solution

  • Maybe you can completely drop lambda-list destructuring... I suggest this solution:

    (defmacro funcall-macro (pairs &body body)
      (handler-case `(progn ,@(mapcar (lambda (pair)
                                        (destructuring-bind (fname arg) pair
                                          (list 'funcall fname arg)))
                                      pairs)
                       ,@body)
        (error (e) (error (format nil "~a~%" e)))))
    

    With destructuring-bind, I check that each pair has exactly two elements.

    Test:

    (macroexpand-1 '(funcall-macro ((f1 a1)
                                  (f2 a2)
                                  (fn an))
                             (b1)
                             (b2)
                             (bn)))
    
    (PROGN (FUNCALL F1 A1) (FUNCALL F2 A2) (FUNCALL FN AN) (B1) (B2) (BN))
    T
    

    This macro also works for pairs equal to ().