我正在尝试编写一个扩展为未指定数量的函数调用的宏,但我也希望能够准确指定要传递给宏调用中每个函数的一个参数。我基本上希望它的语法看起来像一个
let
调用:
(let ((var1 1)
(var2 2)
...
(varn n))
body)
但是将参数传递给函数而不是绑定变量,所以像这样的调用
(foo ((fn1 arg1)
(fn2 arg2)
...
(fnn argn))
body)
扩展为:
(progn
(funcall fn1 arg1)
(funcall fn2 arg2)
...
(funcall fnn argn)
body)
据我所知,宏 lambda 列表的列表解构行为允许我将未指定数量的形式传递给嵌套 lambda 列表中的宏:
(defmacro foo ((&rest call-forms) &body body)
...)
或者为嵌套 lambda 列表中的表单定义严格的语法:
(defmacro foo ((single-fn single-arg) &body body)
...)
但不是两者:
(defmacro foo ((&rest (fn arg)) &body body) ; gibberish
...)
是否有我没有看到的漏洞或解决方法?我知道这看起来很随意,但我上面指定的调用语法对于我正在做的事情来说是理想的。我知道这可能是不可能的,因为
let
是一个特殊的运算符,它的行为似乎是独特的,但我希望被证明是错误的。
不幸的是,不,您无法在 lambda 列表中指定这一点。 通常的做法是指定第一个子句的形式,并记录以下所有子句应具有相同的形式,而不是使用
(&rest clauses)
(这也允许 0 个子句)。所以:
(defmacro foo (((fname argument) &rest other-fname-arguments) &body body)
;; check the form of the others-fname-argument
(handler-case (every (lambda (fname-argument)
(destructuring-bind (fname argument) fname-argument
(declare (ignore fname argument))
t))
other-fname-arguments)
(error ()
(error "The form of the other-fname-argument is not correct, it should be a list of (fname argument).")))
(let ((fname-arguments (cons (list fname argument) other-fname-arguments)))
`(progn
,@(mapcar (lambda (fname-argument)
(destructuring-bind (fname argument) fname-argument
(list 'funcall fname argument)))
fname-arguments)
,@body)))
(setf *print-right-margin* 30)
(pprint (macroexpand-1 '(foo ((f1 a1)
(f2 a2)
(fn an))
(b1)
(b2)
(bn))))
(progn
(funcall f1 a1)
(funcall f2 a2)
(funcall fn an)
(b1)
(b2)
(bn))
也许你可以完全放弃 lambda-list 解构......我建议这个解决方案:
(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)))))
使用
destructuring-bind
,我检查每对是否恰好有两个元素。
测试:
(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
此宏也适用于
pairs
等于 ()
。
one 对,然后扩展为使用相同宏来检查其余部分的形式。执行此操作的一种自然方法是通过模式匹配,此示例使用 destructuring-match
(defmacro foo (clauses &body forms)
(destructuring-match clauses
(()
`(progn ,@forms))
(((f a) &rest more)
`(progn
(funcall ,f ,a)
(foo ,more ,@forms)))
(otherwise
(error "what even is this?"))))
现在(foo ((a 1) (b 2)) x)
将扩展为
(progn
(funcall a 1)
(progn
(funcall b 2)
(progn x)))
这相当于你想要的。无需担心所有嵌套的 progn
:编译器会处理这些。如果
(foo () ...)
不合法,那么你需要这样的东西:
(defmacro foo (clauses &body forms)
(destructuring-match clauses
(((f a))
`(progn
(funcall ,f ,a)
,@forms))
(((f a) &rest more)
`(progn
(funcall ,f ,a)
(foo ,more ,@forms)))
(otherwise
(error "what even is this?"))))
destructuring-match
作为更灵活但可能更慢的
destructuring-bind
。特别是,如果您想要自己的错误消息或条件,您可以实现其他解决方案之一,而无需捕获错误的烦恼:
(defmacro foo (clauses &body forms)
`(progn
,@(mapcar (lambda (clause)
(destructuring-match clause
((f a)
`(funcall ,f ,a))
(otherwise
(error "what even is this?"))))
clauses)
,@forms))
使用任何一种方法,您当然都可以支持更灵活的语法,因此您可以拥有类似于 let
所做的事情(尽管我认为这对于这个特定的宏来说很难正确)。