【问题标题】:Can someone explain the concept of 'hygiene' to me (I'm a scheme programmer)?有人可以向我解释“卫生”的概念(我是一名计划程序员)吗?
【发布时间】:2011-03-01 22:18:42
【问题描述】:

所以...我是方案 r6rs 的新手,并且正在学习宏。谁能给我解释一下“卫生”是什么意思?

提前致谢。

【问题讨论】:

  • 哦,你知道的,洗澡,刷牙,从胡子里挑食物。大多数程序员都遇到了麻烦的事情。
  • @JS:我已经坐在这里好几天了——无论出于何种原因,我都没有离开我的电脑。这就是我努力学习卫生的方式,但我仍然无法掌握这个概念。事实上,我坐在这里的时间越长,情况似乎就越糟:(
  • 我很好奇,你用 Scheme 做什么?
  • @gnucom:为了好玩。此外,我听说(并且正在看到)它可以快速用于原型算法/想法,所以一旦我对它更好,我就会使用它。
  • @incrediman:嘿,这很有趣,这正是我使用它的目的(有趣)。

标签: macros scheme r6rs hygiene


【解决方案1】:

如果你想象一个宏被简单地展开到使用它的地方,那么你也可以想象如果你在你的宏中使用一个变量a,那么可能已经有一个变量a 在使用该宏的地方定义。

不是你想要的a

这样的事情不可能发生的宏观系统被称为卫生

有几种方法可以解决这个问题。一种方法是在宏中使用非常长、非常神秘、非常不可预测的变量名。

一个稍微更精细的版本是其他一些宏系统使用的gensym 方法:而不是 you,程序员提出了一个非常长、非常神秘、非常不可预测的变量名,您可以调用gensym 函数,该函数会为您生成一个非常长、非常神秘、非常不可预测且唯一的变量名称​​

就像我说的,在一个卫生的宏观系统中,这样的冲突一开始就不会发生。 如何使宏观系统卫生本身就是一个有趣的问题,Scheme 社区已经在这个问题上花费了几十年的时间,并且他们不断想出更好的方法来做到这一点。

【讨论】:

  • 挥手 这些不是您要找的as。
  • 这有几个错误的部分:(a) 在宏中使用长而隐秘的名称不是解决方案,它只是延迟宏的一种方式——具体来说,它可以当宏在其内部使用时,会以明显的方式失败。 (b)您只提到问题的一方面,即 gensyms 解决的问题;另一方面是宏定义中的绑定不能被其使用中的绑定所掩盖,例如:(let ((if "bleh")) (my-macro))。这不能仅通过 gensyms 来解决。
【解决方案2】:

这是我发现的。解释它的含义完全是另一回事!

http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-1.html#node_toc_node_sec_12.1

【讨论】:

  • 如果你知道行话,这很简单!这意味着,“卫生宏不会污染符号表”。
  • 这是非常晦涩难懂的行话,而且基本上花了一个研究生课程来了解正在发生的事情(我认为其他人更聪明)。
【解决方案3】:

我很高兴知道这种语言仍在使用!卫生代码是在注入(通过宏)时不会与现有变量发生冲突的代码。

在 Wikipedia 上有很多很好的信息:http://en.wikipedia.org/wiki/Hygienic_macro

【讨论】:

    【解决方案4】:

    卫生通常用于宏的上下文中。卫生宏不使用可能会干扰扩展代码的变量名称。这是一个例子。假设我们要使用宏定义 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's On Lisp 有关于宏的高级资料。

    【讨论】:

      【解决方案5】:

      宏转换代码:它们将一段代码转换为其他代码。作为该转换的一部分,他们可能会用更多代码围绕该代码。如果原始代码引用了变量a,并且在它周围添加的代码定义了a 的新版本,那么原始代码将无法按预期工作,因为它将访问错误的a: if

      (myfunc a)
      

      是原始代码,它期望a是一个整数,宏把X转换成

      (let ((a nil)) X)
      

      那么宏就可以正常工作了

      (myfunc b)
      

      (myfunc a) 将被转换为

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

      这不起作用,因为myfunc 将应用于nil,而不是它所期望的整数。

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

      维基百科对hygienic macros有很好的解释。

      【讨论】:

        【解决方案6】:

        除了提到的所有内容之外,Scheme 的卫生宏还有另一件重要的事情,它来自词法范围。

        假设我们有:

        (syntax-rules () ((_ a b) (+ a b)))
        

        作为宏的一部分,它肯定会插入+,当已经存在+时它也会插入它,但随后是另一个与+含义相同的符号。它将符号绑定到它们在 syntax-rules 所在的词法环境中的值,而不是它的应用位置,毕竟我们是词法范围的。它很可能会在那里插入一个全新的符号,但与+ 具有相同含义的全局绑定的符号位于定义宏的位置。当我们使用如下结构时,这是最方便的:

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

        因此,宏的编写者或用户不必忙于确保其使用顺利。

        【讨论】:

        • +1,这很有趣;这样,它遵循宏编写者和用户的预期行为。
        • 是的,卫生只是确保正确性的一种方式,与作者或用户使用的符号无关。很长一段时间我都在试图弄清楚为什么 (let ((if +)) ...) 没有彻底失败的逻辑,直到我意识到这是因为词法范围。
        猜你喜欢
        • 2014-04-12
        • 2015-05-26
        • 2014-04-01
        • 1970-01-01
        • 2016-11-05
        • 1970-01-01
        • 2011-01-08
        • 2017-03-01
        • 2014-04-20
        相关资源
        最近更新 更多