了解如何实现一次性 lisp 宏 [英] Understanding how to implement once-only lisp macro

查看:16
本文介绍了了解如何实现一次性 lisp 宏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Peter Seibel 的《Practical Common Lisp》一书中,我们可以找到非常复杂的一次性宏的定义(见页面底部 http://www.gigamonkeys.com/book/macros-defining-your-own.html).

In Peter Seibel's book "Practical Common Lisp", we can find the definition of the very complicated macro once-only (see the bottom of page http://www.gigamonkeys.com/book/macros-defining-your-own.html).

我在过去 3 周内第 10 次阅读此宏定义,但无法理解它是如何工作的.:( 更糟糕的是,我无法自己开发这个宏,即使我了解它的用途和使用方法.

I'm reading this macro definition for the 10th times in last 3 weeks and cannot understand how it works. :( Worse, I cannot develop this macro on my own, even though I understand its purpose and how to use it.

我对逐步系统地推导"这个臭名昭著的硬宏特别感兴趣!有什么帮助吗?

I'm especially interested in systematic "derivation" of this notoriously hard macro, step by step! Any help?

推荐答案

你在看这个吗:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))

它并没有那么复杂,但它确实有一个嵌套的反引号,并且有多个相互相似的层级,即使对于有经验的 Lisp 编码人员来说也很容易混淆.

It's not that complicated, but it does have a nested backquote, and multiple levels which are similar to each other, leading to easy confusion, even for experienced Lisp coders.

这是一个宏,用于编写宏的扩展:一个宏,用于编写宏的部分主体.

This is a macro which is used by macros for writing their expansions: a macro which writes parts of the bodies of macros.

在宏本身的主体中有一个普通的 let,然后是一次反引号生成的 let,它将存在于使用 一次.最后,有一个双反引号 let 将出现在 that 宏的宏扩展中,在用户使用该宏的代码站点中.

There is a plain let in the body of the macro itself, then a once-backquoted generated let which will live inside the body of the macro which uses once-only. Finally, there is a doubly backquoted let which will appear in the macro expansion of that macro, in the code site where the macro is used by the user.

两轮生成gensyms是必要的,因为once-only本身就是一个宏,所以为了它自己必须要卫生;所以它会在最外面的let中为自己生成一堆gensyms.而且,once-only 的目的是简化另一个卫生宏的编写.所以它也会为那个宏生成 gensyms.

The two rounds of generating gensyms are necessary because once-only is a macro itself, and so it has to be hygienic for its own sake; so it generates a bunch of gensyms for itself in the outermost let. But also, the purpose of once-only is to simplify the writing of another hygienic macro. So it generates gensyms for that macro also.

简而言之,once-only 需要创建一个宏扩展,它需要一些值为 gensyms 的局部变量.这些局部变量将用于将 gensyms 插入另一个宏扩展中以使其卫生.而且这些局部变量本身必须是卫生的,因为它们是宏扩展,所以它们也是 gensyms.

In a nutshell, once-only needs to create a macro-expansion which requires some local variables whose values are gensyms. Those local variables will be used to insert the gensyms into another macro expansion to make it hygienic. And those local variables have to themselves be hygienic since they are a macro expansion, so they are also gensyms.

如果你正在编写一个普通的宏,你有保存 gensyms 的局部变量,例如:

If you're writing a plain macro, you have local variables which hold gensyms, e.g.:

;; silly example
(defmacro repeat-times (count-form &body forms)
  (let ((counter-sym (gensym)))
    `(loop for ,counter-sym below ,count-form do ,@forms)))

在编写宏的过程中,你发明了一个符号,counter-sym.这个变量是在普通视图中定义的.你,人类,以这样一种方式选择了它,它不会与词汇范围内的任何东西发生冲突.有问题的词法范围是您的宏的词法范围.我们不必担心 counter-sym 会意外捕获 count-formforms 中的引用,因为 forms只是进入一段代码的数据,这些代码最终将插入到某个远程词法范围(使用宏的站点)中.我们必须担心不要将 counter-sym 与宏中的另一个变量混淆.例如,我们不能将我们的局部变量命名为 count-form.为什么?因为该名称是我们的函数参数之一;我们会隐藏它,造成编程错误.

In the process of writing the macro, you have invented a symbol, counter-sym. This variable is defined in plain view. You, the human, have chosen it in such a way that it does not clash with anything in the lexical scope. The lexical scope in question is that of your macro. We don't have to worry about counter-sym accidentally capturing references inside count-form or forms because forms are just data which is going into a piece of code which will end up inserted in some remote lexical scope (the site where the macro is used). We do have to worry about not confusing counter-sym with another variable inside our macro. For instance, we cannot give our local variable the name count-form. Why? Because that name is one of our function arguments; we would shadow it, creating a programming error.

现在,如果您想要一个宏来帮助您编写该宏,那么机器必须完成与您相同的工作.在写代码的时候,它必须要发明一个变量名,而且要小心它发明的名字.

Now if you want a macro to help you write that macro, then the machine has to do the same job as you. When it is writing code, it has to invent a variable name, and it has to be careful about what name it invents.

但是,与您不同,编写代码的机器看不到周围的范围.它不能简单地查看存在哪些变量并选择不冲突的变量.这台机器只是一个函数,它接受一些参数(一段未评估的代码)并生成一段代码,然后在该机器完成其工作后盲目地将其替换到一个作用域中.

However, the code-writing machine, unlike you, does not see the surrounding scope. It cannot simply look at what variables are there and choose ones which do not clash. The machine is just a function which takes some arguments (pieces of unevaluated code) and produces a piece of code that is then blindly substituted into a scope after that machine has done its job.

因此,机器必须格外明智地选择名称.事实上,要完全防弹,它必须是偏执狂并使用完全独特的符号:gensyms.

Therefore, the machine has to choose the names extra wisely. In fact, to be completely bullet proof, it has to be paranoid and use symbols which are completely unique: gensyms.

继续这个例子,假设我们有一个机器人会为我们编写这个宏体.该机器人可以是一个宏,repeat-times-writing-robot:

So continuing with the example, suppose we have a robot which will write this macro body for us. That robot can be a macro, repeat-times-writing-robot:

(defmacro repeat-times (count-form &body forms)
  (repeat-times-writing-robot count-form forms))  ;; macro call

机器人宏可能是什么样的?

What might the robot macro look like?

(defmacro repeat-times-writing-robot (count-form forms)
  (let ((counter-sym-sym (gensym)))     ;; robot's gensym
    `(let ((,counter-sym-sym (gensym))) ;; the ultimate gensym for the loop
      `(loop for ,,counter-sym-sym below ,,count-form do ,@,forms))))

您可以看到它如何具有 once-only 的一些特性:双重嵌套和 (gensym) 的两个层次.如果你能理解这一点,那么到 once-only 的飞跃就很小了.

You can see how this has some of the features of once-only: the double nesting and the two levels of (gensym). If you can understand this, then the leap to once-only is small.

当然,如果我们只是想让机器人编写重复次数,我们可以将其设为函数,然后该函数就不必担心发明变量:它不是宏,所以它不会需要卫生:

Of course, if we just wanted a robot to write repeat-times, we would make it a function, and then that function wouldn't have to worry about inventing variables: it is not a macro and so it doesn't need hygiene:

 ;; i.e. regular code refactoring: a piece of code is moved into a helper function
 (defun repeat-times-writing-robot (count-form forms)
   (let ((counter-sym (gensym)))
     `(loop for ,counter-sym below ,count-form do ,@forms)))

 ;; ... and then called:
(defmacro repeat-times (count-form &body forms)
  (repeat-times-writing-robot count-form forms))  ;; just a function now

但是once-only 不能是一个函数,因为它的工作是代表它的老板,即使用它的宏来发明变量,并且函数不能将变量引入其调用者.

But once-only cannot be a function because its job is to invent variables on behalf of its boss, the macro which uses it, and a function cannot introduce variables into its caller.

这篇关于了解如何实现一次性 lisp 宏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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