有人能向我解释一下“卫生”这个概念吗(我是一个Scheme程序员)?

30

那么……我是scheme r6rs的新手,正在学习宏。有人能解释一下什么是“卫生”吗?

提前感谢。


35
哦,你知道的,洗澡、刷牙、把胡须里的食物弄干净。这些是大多数程序员困难的事情。 - JSBձոգչ
13
@JS: 我已经坐在这里好几天了,为了学习卫生,我没有任何理由离开电脑。这就是我努力学习的方式,但我仍然无法掌握这个概念。事实上,我坐在这里的时间越长,情况似乎越来越糟 :( - Cam
我建议您好好休息一下,这非常有助于理解问题。只有在每晚睡足8.5小时时,我才能更高效、头脑更清晰。 :) - Paul Nathan
1
@gnucom:出于兴趣。另外,我听说(并且正在看到)它在用于原型算法/想法时很快,所以一旦我更加熟练,我会用它来做这个。 - Cam
3
嘿,这很有趣,这正是我(也)用它来(寻找)乐趣的原因。 - sholsapp
显示剩余2条评论
6个回答

26

卫生通常在宏的上下文中使用。一个卫生的宏不使用变量名,以免干扰扩展代码。这里有一个例子。假设我们想用宏定义or特殊形式。直观地说,(or a b c ... d)会扩展为类似于(let ((tmp a)) (if tmp a (or b c ... d)))的东西。(出于简单起见,我省略了空的(or)情况。)

现在,如果像上述草图展开的代码中实际添加了名称tmp,那么它就不是卫生的,也很糟糕,因为它可能会干扰具有相同名称的另一个变量。比如说,我们想要评估:

(let ((tmp 1)) (or #f tmp))

使用我们直观的扩展,这将变得更加简单易懂。

(let ((tmp 1)) (let ((tmp #f)) (if tmp (or tmp)))

这里的宏中的tmp会遮蔽最外层的tmp,因此结果是#f而不是1

现在,如果这个宏是卫生的(在Scheme中,使用syntax-rules时自动成立),那么在扩展中,你会使用一个保证不会出现在代码中任何其他地方的符号名称,而不是使用tmp。在Common Lisp中,你可以使用gensym

Paul Graham的《On Lisp》有关于宏的高级材料。


9
如果你认为宏只是在使用的地方扩展到它所代表的代码,那么你可以想象一下,如果在宏的使用地方已经有一个变量`a`,那么你在宏中使用变量`a`,可能已经存在一个变量`a`了。这不是你想要的`a`!
一个不能发生这种情况的宏系统被称为卫生宏。解决这个问题的方法有几种。一种方法是在宏中使用非常长、非常难懂、非常难预测的变量名。这种方法还有一种稍微更加精细的版本,即一些其他宏系统使用的gensym方法:你可以调用gensym函数,它会为你生成一个非常长、非常难懂、非常难预测且唯一的变量名。
正如我所说,在一个卫生宏系统中,这样的冲突根本不会发生。如何使宏系统卫生本身就是一个有趣的问题,Scheme社区已经花费了数十年时间研究这个问题,并不断提出更好的解决方案。

3
挥手 这不是你要找的 “a”。 - Thanatos
3
这里有几个错误的部分:(a) 在宏中使用冗长、晦涩的名称并不是解决方案,只会延迟宏的执行--特别是当宏在自身内部使用时,可能会出现明显的问题。(b) 你只提到了问题的一面,也就是gensym可以解决的一面;另一方面是,在宏定义中绑定的变量无法被使用时的绑定所遮蔽,例如:(let ((if "bleh")) (my-macro)),这个问题不能仅通过gensym来解决。 - Eli Barzilay

3
我很高兴知道这种语言仍在使用!卫生代码是指通过宏注入时不会与现有变量发生冲突的代码。
维基百科上有许多关于此的好信息:http://en.wikipedia.org/wiki/Hygienic_macro

2
宏转换代码:它们将一个代码位转换为其他内容。作为转换的一部分,它们可能会用更多的代码来包围该代码。如果原始代码引用变量a,并且添加到其周围的代码定义了a的新版本,则原始代码将不能按预期工作,因为它将访问错误的a: 如果
(myfunc a)

这是原始代码,期望 a 是一个整数,而宏将取 X 并将其转换为

(let ((a nil)) X)

然后,该宏将对以下内容正常工作
(myfunc b)

但是(myfunc a)将会被转换为

(let ((a nil)) (myfunc a))

这样做行不通,因为myfunc将被应用于nil而不是它所期望的整数。

卫生宏通过确保使用的名称是唯一的来避免访问错误变量的问题(以及相反方向的类似问题)。

维基百科对卫生宏有很好的解释。


2
除了所有提到的事情,针对Scheme的卫生宏定义还有一个非常重要的因素,这是由词法作用域所决定的。假设我们有如下代码:
(syntax-rules () ((_ a b) (+ a b)))

作为宏的一部分,它肯定会插入 +,如果已经有 + 了,它也会插入另一个具有与+相同含义的符号。它将符号绑定到它们在语法规则所在的词法环境中的值,并不是应用它的位置,毕竟我们是词法作用域。它很可能会在那里插入一个全新的符号,但该符号与在定义宏的地方全局绑定的+相同。当我们使用这样的结构时,它非常方便:

(let ((+ *))
  ; piece of code that is transformed
)

因此,编写宏的作者或用户无需担心其使用是否顺利。


+1,这很有趣;这样可以符合宏编写者和用户的预期行为。 - Cam
是的,卫生是确保正确性的一种方式,与作者或用户使用的符号无关。我曾经花了很长时间来思考为什么(let ((if +)) ...)这样的代码没有严重失败,直到我意识到这是因为词法作用域。 - Zorf

2

1
如果你懂行话,这句话就很简单明了!它的意思是,“卫生宏不会污染符号表”。 - Paul Nathan
这是非常晦涩和复杂的术语,基本上需要我上研究生课程才能理解它的含义(我认为其他人可能更聪明)。 - Paul Nathan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接