繁体   English   中英

用不同的表达式系统地调用 Common Lisp 宏

[英]Invoking Common Lisp macros systematically with varying expressions

我正在学习 Common Lisp (SBCL)。
我想创建一个工具来调用两个(或更多)具有几个仅在某些参数上不同的相似表达式的宏。

我想定义表达式的基础,然后用我提供的参数修改它。 为此,我想到了 lambda function 的定义。

据我所知,没有与宏的funcall类似的东西,所以我也将宏包装在 lambda 中。

我觉得我对所有这些lambda -s 和funcall -s 过于复杂。 有没有更优雅的方式?

宏来自外部库,所以我不想修改它们。
(特别是 fiveam 测试库的finishessignals 。)

这是一个示例代码:

(defmacro macro1 (body) ())
(defmacro macro2 (body) ())

(defun check-expr-with-args (do-m func args)
  (dolist (arg args)
    (format t "~a " arg)
    (funcall do-m (lambda () (funcall func arg)))))

(let ((test-expr 
        #'(lambda (val) (format t "~a" val)))
      (cases (list
               (list #'(lambda (func) ( macro1 (funcall func))) 
                     (list 1 2 3 4 5))
               (list #'(lambda (func) ( macro2 (funcall func)))
                     (list -4 -5 -6 -7 -8 -9)))))
  (dolist (c cases)
    (check-expr-with-args (first c) test-expr (second c))))

最初我试图将宏名称传递给我的check-expr-with-args function,以及引用形式的表达式,依赖于参数插入的词法范围。 那没有成功。

我认为您可以编写一个包装器宏来生成调用macro1 (和macro2 )的代码。 例如,我在这里定义m1 ,它采用 (i) 测试表达式和 (ii) 预期在运行时计算值列表的表达式。

(defmacro m1 (test-expr input-expr)
  (let ((arg (gensym)))
    `(dolist (,arg ,input-expr)
       (macro1 ,test-expr ,arg))))

test-exprinput-expr都被注入到一个dolist表达式中,它绑定了一个名为arg的变量。 这里arg是与gensym一起引入的新符号,以避免意外遮盖可能在test-expr中使用的变量或符号宏。

例如:

(m1 (some-test-p) (list 1 2 3 4))

以上展开为:

(DOLIST (#:G1909 (LIST 1 2 3 4)) 
  (MACRO1 (SOME-TEST-P) #:G1909))

结果表达式包含MACRO1 ,它也将被扩展。 但它现在被包裹在一个表达式中,该表达式迭代运行时计算的一些列表。 在这里,它是一个常量,但您可以将其替换为任何其他表达式。

总之,通常最好通过将您自己的宏扩展到其他宏来在宏级别组合宏。

我想创建一个工具来调用两个(或更多)宏

(defmacro invoke-macros (macro-forms)
  `(progn ,@macro-forms))

具有几个相似的表达式,仅在某些参数上有所不同。

(defmacro invoke-macros (macro-names &rest macro-arguments)
  `(progn ,@(loop for m in macro-names
                  appending (loop for a in macro-arguments
                                  collecting `(,m ,@a)))))

查看:

[1]> (macroexpand '(invoke-macros (m1 m2) (a b c) (d e f)))
(PROGN (M1 A B C) (M1 D E F) (M2 A B C) (M2 D E F)) ;
T

当然,这适用于任何运算符,包括函数,而不仅仅是宏; 我们应该称之为invoke-operators 或者一些更好的名字,反映我们正在从运算符和参数语法创建笛卡尔积。

如果我们需要返回值,我们可以将progn更改为list 或者如果预计组合的数量不会很大,则可能是values

如果这必须是 function:

(defun invoke-ops (op-names &rest op-arguments)
  (loop for o in op-names
            appending (loop for a in op-arguments
                            collecting (eval `(,o ,@a)))))

查看:

[1]> (invoke-ops '(list +) '(1 2) '(10 20))
((1 2) (10 20) 3 30)

由于invoke-ops现在是 function,我们必须引用 arguments。

宏没有funcall 如果您可以访问宏的扩展器 function,则有一个函数调用,但它所做的只是执行代码转换; 它不会调用宏。

调用由宏生成的代码的唯一方法是:您可以eval代码,或者您可以compile代码并funcall它。 后一种方法需要 function,因此您首先将代码放入lambda表达式中:

(funcall (compile nil `(lambda () ,code-output-by-macro)))

compilenil参数是 function 名称; 我们告诉compile器我们没有处理命名为 function 的定义。 我们在第二个参数中提供代码。 在某些 Common Lisp 实现中,没有求值器; eval function 做了类似的事情:

(defun eval (code)
  (funcall (compile nil `(lambda () ,code))))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM