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

huangapple go评论54阅读模式
英文:

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

问题

我正在尝试编写一个宏,它会展开为不确定数量的函数调用,但我也想能够指定在宏调用中传递给每个函数的确切参数。基本上,我希望它的语法看起来像一个 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) ; 无效的语法
  ...)

是否存在我没有注意到的漏洞或解决方法?我知道这似乎是武断的,但我上面指定的调用语法对我正在做的事情来说是理想的。我明白这可能行不通,因为 let 是一个特殊的运算符,其行为似乎是独特的,但我很愿意被证明是错误的。

英文:

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.

答案1

得分: 2

抱歉,无法为代码部分提供翻译。如果您需要有关代码的解释或帮助,请随时提出具体问题,我会尽力提供帮助。

英文:

Unfortunately, no, you won't be able to specify that in the lambda-list.
What is customary to do, instead of using (&rest clauses), (which would also allow for 0 clauses), is to specify the form of the first clause, and document that all the following should have the same form. So:

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

答案2

得分: 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,我检查每个pair是否确切包含两个元素。

测试:

(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等于()

英文:

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

答案3

得分: 0

另一种处理这个问题的有趣方法是编写一个宏,它检查*一个*对,并扩展成一个使用相同宏来检查其余部分的形式。做到这一点的一种自然方式是通过模式匹配,这个示例使用了 [`destructuring-match`](https://tfeb.github.io/dsm/) 来实现这一点,因为它专门设计用于以这种方式处理宏形式。

(defmacro foo (clauses &body forms)
  (destructuring-match clauses
    (()
     `(progn ,@forms))
    (((f a) &rest more)
     `(progn
        (funcall ,f ,a)
        (foo ,more ,@forms)))
    (otherwise
     (error "这到底是什么?"))))

现在 `(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 "这到底是什么?"))))

---

你还可以将 `destructuring-match` 用作更灵活但可能较慢的 `destructuring-bind`。特别是,你可以实现另一种解决方案,而不必担心捕获错误,如果你想要自己的错误消息或条件:

(defmacro foo (clauses &body forms)
  `(progn
     ,@(mapcar (lambda (clause)
                 (destructuring-match clause
                   ((f a)
                    `(funcall ,f ,a))
                   (otherwise
                    (error "这到底是什么?"))))
               clauses)
     ,@forms))

使用任一种方法,你当然可以支持更灵活的语法,因此你可以像 `let` 做的那样有一些东西(尽管我认为对于这个特定的宏来说,这可能很难做到)。

<details>
<summary>英文:</summary>

One other interesting way to deal with this problem is to write a macro which checks *one* pair and then expands into a form which uses the same macro to check the rest.  A natural way to do this is by pattern matching, and this example uses [`destructuring-match`](https://tfeb.github.io/dsm/) to do this, as it was specifically designed for processing macro forms in this way.

(defmacro foo (clauses &body forms)
(destructuring-match clauses
(()
(progn ,@forms))
(((f a) &amp;rest more)
(progn
(funcall ,f ,a)
(foo ,more ,@forms)))
(otherwise
(error "what even is this?"))))


Now `(foo ((a 1) (b 2)) x)` will expand into

(progn
(funcall a 1)
(progn
(funcall b 2)
(progn x)))


Which is equivalent to what you want.  There&#39;s no need to worry about all the nested `progn`s: the compiler will deal with those. 

If `(foo () ...)` should not be legal, then you need something like this:

(defmacro foo (clauses &body forms)
(destructuring-match clauses
(((f a))
(progn
(funcall ,f ,a)
,@forms))
(((f a) &amp;rest more)
(progn
(funcall ,f ,a)
(foo ,more ,@forms)))
(otherwise
(error "what even is this?"))))


---

You can also use `destructuring-match` as a more flexible but probably slower `destructuring-bind`.  In particular you can implement one of the other solutions without the annoyance of having to catch the error if you want your own error message or condition:

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


Using either approach you can support more flexible syntax of course, so you could have something like what `let` does (although this would be hard to get right for this specific macro I think).

</details>



huangapple
  • 本文由 发表于 2023年7月28日 02:33:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76782542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定