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

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

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

问题

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

  1. (let ((var1 1)
  2. (var2 2)
  3. ...
  4. (varn n))
  5. body)

但是传递参数给函数,而不是绑定变量,因此像这样的调用:

  1. (foo ((fn1 arg1)
  2. (fn2 arg2)
  3. ...
  4. (fnn argn))
  5. body)

展开为这样:

  1. (progn
  2. (funcall fn1 arg1)
  3. (funcall fn2 arg2)
  4. ...
  5. (funcall fnn argn)
  6. body)

据我所知,宏 lambda 列表的列表解构行为允许我在嵌套的 lambda 列表中将不确定数量的表单传递给宏:

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

或者定义一个嵌套 lambda 列表中表单的严格语法:

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

但不能同时使用这两种方式:

  1. (defmacro foo ((&rest (fn arg)) &body body) ; 无效的语法
  2. ...)

是否存在我没有注意到的漏洞或解决方法?我知道这似乎是武断的,但我上面指定的调用语法对我正在做的事情来说是理想的。我明白这可能行不通,因为 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:

  1. (let ((var1 1)
  2. (var2 2)
  3. ...
  4. (varn n))
  5. body)

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

  1. (foo ((fn1 arg1)
  2. (fn2 arg2)
  3. ...
  4. (fnn argn))
  5. body)

expands to this:

  1. (progn
  2. (funcall fn1 arg1)
  3. (funcall fn2 arg2)
  4. ...
  5. (funcall fnn argn)
  6. 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:

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

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

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

but NOT both:

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

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:

  1. (defmacro foo (((fname argument) &rest other-fname-arguments) &body body)
  2. ;; check the form of the others-fname-argument
  3. (handler-case (every (lambda (fname-argument)
  4. (destructuring-bind (fname argument) fname-argument
  5. (declare (ignore fname argument))
  6. t))
  7. other-fname-arguments)
  8. (error ()
  9. (error "The form of the other-fname-argument is not correct, it should be a list of (fname argument).")))
  10. (let ((fname-arguments (cons (list fname argument) other-fname-arguments)))
  11. `(progn
  12. ,@(mapcar (lambda (fname-argument)
  13. (destructuring-bind (fname argument) fname-argument
  14. (list 'funcall fname argument)))
  15. fname-arguments)
  16. ,@body)))
  17. (setf *print-right-margin* 30)
  18. (pprint (macroexpand-1 '(foo ((f1 a1)
  19. (f2 a2)
  20. (fn an))
  21. (b1)
  22. (b2)
  23. (bn))))
  24. (progn
  25. (funcall f1 a1)
  26. (funcall f2 a2)
  27. (funcall fn an)
  28. (b1)
  29. (b2)
  30. (bn))

答案2

得分: 0

也许你可以完全取消lambda-list解构...我建议这个解决方案:

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

使用destructuring-bind,我检查每个pair是否确切包含两个元素。

测试:

  1. (macroexpand-1 '(funcall-macro ((f1 a1)
  2. (f2 a2)
  3. (fn an))
  4. (b1)
  5. (b2)
  6. (bn)))
  7. (PROGN (FUNCALL F1 A1) (FUNCALL F2 A2) (FUNCALL FN AN) (B1) (B2) (BN))
  8. T

这个宏也适用于pairs等于()

英文:

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

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

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

Test:

  1. (macroexpand-1 '(funcall-macro ((f1 a1)
  2. (f2 a2)
  3. (fn an))
  4. (b1)
  5. (b2)
  6. (bn)))
  7. (PROGN (FUNCALL F1 A1) (FUNCALL F2 A2) (FUNCALL FN AN) (B1) (B2) (BN))
  8. T

This macro also works for pairs equal to ().

答案3

得分: 0

  1. 另一种处理这个问题的有趣方法是编写一个宏,它检查*一个*对,并扩展成一个使用相同宏来检查其余部分的形式。做到这一点的一种自然方式是通过模式匹配,这个示例使用了 [`destructuring-match`](https://tfeb.github.io/dsm/) 来实现这一点,因为它专门设计用于以这种方式处理宏形式。
  2. (defmacro foo (clauses &body forms)
  3. (destructuring-match clauses
  4. (()
  5. `(progn ,@forms))
  6. (((f a) &rest more)
  7. `(progn
  8. (funcall ,f ,a)
  9. (foo ,more ,@forms)))
  10. (otherwise
  11. (error "这到底是什么?"))))
  12. 现在 `(foo ((a 1) (b 2)) x)` 将会扩展成
  13. (progn
  14. (funcall a 1)
  15. (progn
  16. (funcall b 2)
  17. (progn x)))
  18. 这与你想要的等价。无需担心所有嵌套的 `progn`:编译器会处理这些。
  19. 如果 `(foo () ...)` 不应该是合法的,那么你需要像这样:
  20. (defmacro foo (clauses &body forms)
  21. (destructuring-match clauses
  22. (((f a))
  23. `(progn
  24. (funcall ,f ,a)
  25. ,@forms))
  26. (((f a) &rest more)
  27. `(progn
  28. (funcall ,f ,a)
  29. (foo ,more ,@forms)))
  30. (otherwise
  31. (error "这到底是什么?"))))
  32. ---
  33. 你还可以将 `destructuring-match` 用作更灵活但可能较慢的 `destructuring-bind`。特别是,你可以实现另一种解决方案,而不必担心捕获错误,如果你想要自己的错误消息或条件:
  34. (defmacro foo (clauses &body forms)
  35. `(progn
  36. ,@(mapcar (lambda (clause)
  37. (destructuring-match clause
  38. ((f a)
  39. `(funcall ,f ,a))
  40. (otherwise
  41. (error "这到底是什么?"))))
  42. clauses)
  43. ,@forms))
  44. 使用任一种方法,你当然可以支持更灵活的语法,因此你可以像 `let` 做的那样有一些东西(尽管我认为对于这个特定的宏来说,这可能很难做到)。
  45. <details>
  46. <summary>英文:</summary>
  47. 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?"))))

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

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

  1. 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.
  2. 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?"))))

  1. ---
  2. 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))

  1. 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).
  2. </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:

确定