Common Lisp:如何用宏构建循环表达式?

时间:2012-02-10 02:10:04

标签: loops macros common-lisp

这是一个related question,某种跟进。

假设我正在尝试使用宏来构建循环表达式,其中生成的循环表达式取决于参数是否为列表:

(defmacro testing-loop (var)
   `(eval (append '(loop for x from 0 to 5)
            (when (consp ,var) '(and y in ,var))            
            '(collect)            
            (if (consp ,var) '(y) '(x))))

这似乎有效:

CL-USER> (testing-loop 2)
(0 1 2 3 4 5)
CL-USER> (testing-loop (list 5 6 7))
(5 6 7)

但是当在词法闭包中应用这个宏时,它会崩溃:

CL-USER> (let ((bar (list 1 2 3)))
           (testing-loop bar))

抛出未定义的变量:BAR

我希望testing-loop宏扩展到绑定栏的词法范围?

1 个答案:

答案 0 :(得分:4)

@mck,我明白为什么你现在要使用eval。但是,正如我在上一个问题的答案中提到的那样,这是一个非常混乱的解决方案,速度很慢。经典的 On Lisp eval

这样说

“通常,在运行时调用eval不是一个好主意,原因有两个:

  1. 这是效率低下的:eval是一个原始列表,要么必须在它上面编译它 现场,或在口译员中评估。无论哪种方式都比编译慢 事先编写代码,然后调用它。

  2. 功能不强,因为表达式的评估没有词汇上下文。 除此之外,这意味着你不能参考普通的 在被评估的表达式之外可见的变量。

  3. 通常,明确地调用eval就像在机场礼品店购买东西一样。 等到最后一刻,你必须支付高价 选择二流商品。“

    在这种情况下,最简单的事情就是:

    (defmacro testing-loop (var)
      (let ((g (gensym)))
       `(let ((,g ,var))
          (if (consp ,g)
            (loop for x from 0 to 5 collect x)
            (loop for x from 0 to 5 and y in ,g collect y)))))
    

    我知道你想要分解公共loop for x from 0 to 5(无论如何在第二个分支中实际上并不需要)。但是loop本身就是一个宏,它在编译时将转换为高效的低级代码。因此,必须使用编译时可用的值在编译时构建对loop的调用。您不能在其中插入(if),以便在运行时进行评估。

    如果你真的不想重复loop for x from 0 to 5,你可以这样做:

    (let ((a '(loop for x from 0 to 5)))
      `(if (consp ,var)
           (,@a collect x)
           (,@a and y in ,var collect y)))
    

    这只是为了给你这个想法;如果你真的这样做,请确保gensym

    从中学到的一个好教训是:在编写宏时,需要清楚地记住编译时发生的情况以及运行时发生的情况。您使用eval编写的宏会根据loop的返回值动态编译consp宏,每次运行。你真的想编译两个不同的loop宏,只需在运行时选择正确的宏。