我可以在 Lisp 宏 lambda 列表中拥有不确定数量的解构列表吗?

问题描述 投票:0回答:3

我正在尝试编写一个扩展为未指定数量的函数调用的宏,但我也希望能够准确指定要传递给宏调用中每个函数的一个参数。我基本上希望它的语法看起来像一个

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
是一个特殊的运算符,它的行为似乎是独特的,但我希望被证明是错误的。

macros common-lisp destructuring clisp
3个回答
2
投票

不幸的是,不,您无法在 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))

0
投票

也许你可以完全放弃 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
等于
()


0
投票
处理这个问题的另一种有趣的方法是编写一个宏来检查

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

 所做的事情(尽管我认为这对于这个特定的宏来说很难正确)。

© www.soinside.com 2019 - 2024. All rights reserved.