关于第一个问题 - 有在计划全局没有单一的惯例。有些人使用*foo*
(因为与CL中的命名约定不同,正如我在上面的评论中所说的),有些人使用CAPITALS(在大小写敏感的Schemes中),但都不够流行以至于不被视为惯例。
第二个问题更为微妙。是的,有一个单一的全局命名空间的缺陷。这些错误与Javascript 和中的错误相同,如Common Lisp中所述:加载随机代码可能会改变其他代码的含义 - 因此,如果我要加载两个库,并且都定义了一些foo
全局函数,那么这些库中的一个会打破另一个。这不是一个新问题,并且有各种方式来处理它。
最明显的做法是避免使用不属于公共接口的全局变量。因此,例如,而不是
(define helper ...)
(define foo ...)
你写
(define foo
(let()
(define helper ...)
(define foo ...)
foo))
,如果你在你的界面具有多种功能,你做同样的事情(例如,用球拍的define-values
或使用功能的列表)。请注意,这具有双重保护:helper
之类的内部事物不会污染全局命名空间,因此它不会破坏其他代码 - 而定义helper
的其他代码不会破坏此代码。这也包括foo
,以防递归调用。
当你想要快速编写这样的代码时,一个相关的黑客就是编写这样的代码:在第一个版本中,Scheme编译器无法将foo
编译成快速循环,因为绑定稍后可能会发生变化,但使用第二个版本优化是可能的。
当您没有模块系统时,另一种防御性编程形式是获取对您的代码非常重要的值。例如,你可以这样做:
(define foo
(let ([+ +] [- -] [* *] [/ /] ... more ...)
(define helper ...)
(define foo ...)
foo))
现在的代码有自己的不可变绑定的算术运算,它保护它免受变化在未来这些操作,并允许算术的安全优化,在此代码。
所有这些都是一种穷人模块系统的约定,这与Javascript中常见的(function() { ... })()
类似。但是,当然,一个合适的模块系统使得这更方便,更好地运行。较新的Javascript版本和较新的Scheme版本都面临着某种模块系统的这个问题,而在Scheme中,趋势是仅使用模块并避免load
为不可靠的黑客攻击。因为你可能会覆盖全局变量或函数从其他文件覆盖。解决方法是与CL的包系统,它可以采取行动作为一种弱模块系统,它的弱点在于它给你提供了单独命名空间的便利,但是命名空间仍然是全局的,这意味着优化仍然非常困难。换句话说,这些命名空间不会给你传统模块系统具有“封闭的世界假设”,这意味着编译器仍然不能假定绑定不会改变。)
@ [Will Ness](http://stackoverflow.com/users/849891/willness )哈哈忘记了那些无言者?你有什么经验?我真的不想从一开始就弄错我的代码。 – usernvk 2013-04-29 18:28:07
@WillNess:'* foo *'约定不适用于全局变量,它适用于特殊变量,因为如果名称用于某个地方的本地词法作用域中,那么特殊声明对代码可能是灾难性的。一些Schemers也将它用于具有特定状态的全局变量,如自定义全局变量。 – 2013-04-30 06:04:31
@EliBarzilay感谢您的澄清。 :) – 2013-04-30 08:12:34