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

Loops Common Lisp:如何使用宏构建循环表达式?,loops,macros,common-lisp,Loops,Macros,Common Lisp,这是一个,某种形式的跟进 假设我试图使用宏构建循环表达式,其中生成的循环表达式取决于参数是否为列表: (defmacro testing-loop (var) `(eval (append '(loop for x from 0 to 5) (when (consp ,var) '(and y in ,var)) '(collect) (if (consp ,var) '

这是一个,某种形式的跟进

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

(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


我期望
测试循环
宏扩展到bar绑定的词法范围

@mck,我明白你为什么现在要使用
eval
。但这是一个非常混乱的解决方案,而且很慢,正如我在回答前一个问题时提到的。Lisp上的经典之作是关于
eval

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

  • 这是低效的:eval被交给了一个原始列表,并且要么必须在计算机上编译它 定位,或在解释器中对其进行评估。任何一种方法都比编译慢 事先输入代码,然后调用它

  • 它没有那么强大,因为表达式是在没有词汇上下文的情况下计算的。 除此之外,这意味着你不能参考普通的 在正在计算的表达式外部可见的变量

  • 通常,直接打电话给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)))))
    
    我知道您希望将x的公共
    循环从0分解到5(在第二个分支中实际上并不需要)。但是
    loop
    本身就是一个宏,它在编译时被转换成高效的低级代码。因此,对
    循环的调用必须在编译时生成,使用编译时可用的值。您不能只在其中插入一个
    (if)
    ,它将在运行时进行评估

    如果您确实不想在0到5之间重复x的
    循环,可以执行以下操作:

    (let ((a '(loop for x from 0 to 5)))
      `(if (consp ,var)
           (,@a collect x)
           (,@a and y in ,var collect y)))
    
    这只是给你一个想法;如果确实要这样做,请确保
    gensym


    从中可以学到一个很好的教训:编写宏时,需要清楚地记住编译时发生的事情和运行时发生的事情。使用
    eval
    编写的宏每次运行时都会根据
    consp
    的返回值动态编译
    循环
    宏。你真的想编译两个不同的
    循环
    宏一次,然后在运行时选择正确的宏。

    @mck,我明白你为什么现在要使用
    eval
    。但这是一个非常混乱的解决方案,而且很慢,正如我在回答前一个问题时提到的。Lisp上的经典之作是关于
    eval

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

  • 这是低效的:eval被交给了一个原始列表,并且要么必须在计算机上编译它 定位,或在解释器中对其进行评估。任何一种方法都比编译慢 事先输入代码,然后调用它

  • 它没有那么强大,因为表达式是在没有词汇上下文的情况下计算的。 除此之外,这意味着你不能参考普通的 在正在计算的表达式外部可见的变量

  • 通常,直接打电话给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)))))
    
    我知道您希望将x的公共
    循环从0分解到5(在第二个分支中实际上并不需要)。但是
    loop
    本身就是一个宏,它在编译时被转换成高效的低级代码。因此,对
    循环的调用必须在编译时生成,使用编译时可用的值。您不能只在其中插入一个
    (if)
    ,它将在运行时进行评估

    如果您确实不想在0到5之间重复x的
    循环,可以执行以下操作:

    (let ((a '(loop for x from 0 to 5)))
      `(if (consp ,var)
           (,@a collect x)
           (,@a and y in ,var collect y)))
    
    这只是给你一个想法;如果确实要这样做,请确保
    gensym


    从中可以学到一个很好的教训:编写宏时,需要清楚地记住编译时发生的事情和运行时发生的事情。使用
    eval
    编写的宏每次运行时都会根据
    consp
    的返回值动态编译
    循环
    宏。您确实希望编译两个不同的
    循环
    宏一次,并在运行时选择正确的宏。

    您可以在那里调用
    EVAL
    EVAL
    始终在空词汇环境中计算表单。坦白地说,我真的不明白为什么你一开始就在那里进行评估。@Elias Mårtenson:我认为
    eval
    是在回答上一个问题时向mck建议的@麦克:如果你想通过找出宏的用例来学习宏,然后为这些用例找到解决方案,也许你应该阅读和/或。它们涵盖了很多有趣的领域——不过我建议从OnLisp开始。@EliasMårtenson:谢谢你的评论,我不知道
    eval
    在空词汇环境中求值;我学到了一些新东西。但是,如果我没有将
    eval
    放在那里,宏将返回代码列表。我做错了什么?@MironBrezuleanu:谢谢你分享消息来源!您有一个呼叫呼叫
    EVAL
    EVAL
    始终在空词汇环境中计算表单。坦白地说,我真的不明白为什么你一开始就在那里进行评估。@Elias Mårtenson:我认为
    eval
    是在回答上一个问题时向mck建议的@麦克:如果你想通过找出t的用例来学习宏