Common Lisp:如何使用宏构建循环表达式? [英] Common Lisp: How to build a loop expression with a macro?

查看:57
本文介绍了Common Lisp:如何使用宏构建循环表达式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是相关问题,进行某种跟进.

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

Let's say I'm trying to build a loop expression by using macros, in which the resulting loop expression is dependant on whether the parameter is a list:

(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)

但是在词汇闭包中应用此宏时,它会崩溃:

But when applying this macro in a lexical closure, it breaks down:

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

会抛出未定义的变量:BAR

which throws undefined variable: BAR

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

I expected testing-loop to macroexpand into the lexical scope where bar is bound?

推荐答案

@mck,我明白了为什么现在要使用eval的原因.但这是一个非常混乱且缓慢的解决方案,正如我在回答上一个问题时所提到的那样.经典的在Lisp上说了关于eval的这一点:

@mck, I see why you want to use eval now. But it's a very messy solution, and slow, as I mentioned in my answer to the previous question. The classic On Lisp says this about eval:

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

"Generally it is not a good idea to call eval at runtime, for two reasons:

  1. 效率低下:eva​​l收到了一个原始列表,要么必须将其编译到 当场,或在口译员那里对其进行评估.两种方法都比编译慢 代码,然后调用它.

  1. It’s inefficient: eval is handed a raw list, and either has to compile it on the spot, or evaluate it in an interpreter. Either way is slower than compiling the code beforehand, and just calling it.

功能不那么强大,因为表达式是在没有词法上下文的情况下进行评估的. 除其他外,这意味着您不能提及普通人 在要求值的表达式外部可见的变量.

It’s less powerful, because the expression is evaluated with no lexical context. Among other things, this means that you can’t refer to ordinary variables visible outside the expression being evaluated.

通常,显式调用eval就像在机场礼品店购买东西. 等到最后一刻,您必须为有限的价格付出高昂的代价 选择二等商品."

Usually, calling eval explicitly is like buying something in an airport gift-shop. Having waited till the last moment, you have to pay high prices for a limited selection of second-rate goods."

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

In this case the simplest thing is just to:

(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).

I know you want to factor out the common loop for x from 0 to 5 (which isn't actually needed in the second branch anyways). But loop is itself a macro which is converted at compile time to efficient, low level code. So the call to loop has to be built at compile time, using values which are available at compile time. You can't just insert an (if) in there which is intended to be evaluated at run time.

如果您真的不想重复loop for x from 0 to 5,则可以执行以下操作:

If you really don't want to repeat loop for x from 0 to 5, you could do something like:

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

这只是为了给您这个主意;如果您确实这样做,请确保gensym

That's just to give you the idea; if you really do this, make sure to gensym!

一个值得借鉴的好教训是:编写宏时,需要牢记在编译时发生的事情以及在运行时发生的事情.您使用eval编写的宏会根据consp的返回值,在每次运行 时动态地编译loop宏.您确实要一次编译2个不同的loop宏,然后在运行时选择正确的宏即可.

A good lesson to learn from this is: when you are writing macros, you need to keep clearly in mind what is happening at compile time and what is happening at run time. The macro you wrote with eval compiles the loop macro dynamically, every time it is run, based on the return value of consp. You really want to compile the 2 different loop macros once, and just select the correct one at run-time.

这篇关于Common Lisp:如何使用宏构建循环表达式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆