英文:
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))
(progn
(((f a) &rest more)
(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'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
(progn
(funcall ,f ,a)
,@forms))
(((f a) &rest more)
(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
(funcall ,f ,a))
,@(mapcar (lambda (clause)
(destructuring-match clause
((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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论