【问题标题】:How to pass a list to macro in common lisp?如何将列表传递给普通 lisp 中的宏?
【发布时间】:2012-11-02 13:21:35
【问题描述】:

我正在尝试将列表传递给宏,例如:

(defmacro print-lst (lst)
  `(progn
     ,@(mapcar #'(lambda (x) `(print ,x)) lst)))
(let ((lst '(1 2 3)))
      (print-lst lst))

它发现错误:“值 LST 不是 LST 类型”。

那么,我的问题是,这段代码有什么问题以及如何将列表传递给宏?

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

    我不确定为什么要将它定义为宏而不是常规函数,但问题是宏不评估它们的参数。如果你给它一个词法变量的名字,它看到的只是名字('LST),而不是绑定的值。它(正确地)抱怨符号 'LST 不是一个列表,因此不是 MAPCAR 的有效第二个参数。

    您可以将其称为(print-lst (1 2 3)),但是您可以不使用宏而只使用(mapc #'print lst)

    【讨论】:

      【解决方案2】:

      你试图用你的宏做的是扩展一个文字列表。

      不评估宏参数。所以,print-lst 实际上是接收符号 lst,而不是绑定到变量的列表。

      您要么知道这一点并给print-lst 一个文字列表,要么您可以生成评估宏参数的代码:

      (defmacro print-lst (lst)
        (let ((item (gensym)))
          ;; Macros usually make sure that expanded arguments are
          ;; evaluated only once and in left-to-right order.
          ;; 
          ;; In this case, we only have one argument and we only evaluate it once.
          `(dolist (,item ,lst)
             (print ,item))))
      

      虽然这显然不是一个很好的宏示例,但它最好是一个函数:

      (defun print-lst (lst)
        (dolist (item lst)
          (print item)))
      

      如果你想内联调用print-lst,你可以查阅你的实现文档,看看它是否关注(declaim (inline print-lst))

      另一种选择是使用编译器宏作为函数的补充,以内联调用,其中参数的评估是编译时的已知值,但再次查看您的实现是否关注编译器宏:

      (define-compiler-macro print-lst (&whole form lst &environment env)
        (declare (ignorable env))
        ;; Some implementations have an eval function that takes an environment.
        ;; Since that's not standard Common Lisp, we don't use it in constantp.
        (if (constantp lst)
            `(progn
               ,@(mapcar #'(lambda (item)
                             `(print ,item))
                         (eval lst)))
            ;; Return the original form to state you didn't transform code.
            form))
      

      【讨论】:

        【解决方案3】:

        您的代码不必要地使用了宏。实际上,您可以像上面提到的那样 eval (mapcar #'print '(1 2 3)) 。您也可以使用 (print-lst (1 2 3)) 而不使用列表引号,但不建议这样做,因为这不符合使用参数调用函数的一般做法。

        总而言之,在调用宏时,如果您希望对其进行评估,则不应引用没有 eval 逗号 ',' 的符号,因为它将被字面上替换到宏模板中.

        例如

        (setq a 1)
        
        ;;A won't be evaluated without the comma
        (defmacro foo (x) `(progn ,(print x))
        (foo 'a) ;;=> 'A (A is returned since (print 'a) is evaluated)
        (foo a) ;;=> A (1 is returned since (print a) is evaluated)
        
        ;;A is evaluated
        (defmacro bar (x) `(print ,x))
        (bar 'a) ;;=> A
        (bar a) ;;=> 1
        

        您的代码:

        (defmacro print-lst (lst)                                                                                    
          `(progn                                                                                                    
             ,@(mapcar #'(lambda (x) `(print ,x)) lst)))
        

        “mapcar”表单将作为一个整体进行评估,但“lst”只会替换您传入的内容。

        我可以定义一个函数而不是一个宏来解决这个问题:

        (defun print-lst (lst)
          (mapcar #'eval
            (mapcar #'(lambda (x) `(print ,x)) lst)))
        

        然后:

        (let ((lst '(1 2 3)))
          (print-lst lst))
        
        ;;=>
        1
        2
        3
        (1 2 3)
        

        如果您对标题中的问题感兴趣,可以查看我对此的另一个答案:

        How do I apply "or" to a list in elisp

        【讨论】:

          猜你喜欢
          • 2019-11-03
          • 1970-01-01
          • 1970-01-01
          • 2017-01-24
          • 1970-01-01
          • 2013-07-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多