根据 http://www.phyast.pitt.edu/~micheles/scheme/scheme29.html
“值得一提的是,如果你在实践中使用包系统(比如在 Common Lisp 中)或命名空间系统(比如在 Clojure 中),变量捕获变得非常罕见。在使用模块系统的 Scheme 中,卫生是必不可少的。 .”
这里使用的术语包系统、命名空间系统和模块系统之间有什么区别?
请注意,这不是关于 Lisp-1 与 Lisp-2(链接文档单独讨论)。我猜这可能与 Common Lisp 中的方式有关,尝试在两个不同的包中使用相同的符号可以获得两个具有相同名称的不同符号。
最佳答案
我认为包系统和模块系统之间的共同区别是包系统处理将源文本映射到名称,而模块系统处理将名称映射到含义。 (我不知道命名空间系统是什么,但我怀疑它是一个包系统,可能没有一流的包,甚至没有一流的名称?)
在 Lisp 的上下文中,名称是符号,所以当你阅读具有符号语法的东西时,包系统控制你得到什么符号(特别是它在哪个包中),而在模块系统中你总是得到相同的符号但它的值取决于模块状态。
下面是一个使用 CL 包系统和 Racket 的模块系统的例子,说明了这些东西的不同之处。请注意,我是 CL 人:我对 CL 包系统的了解比对 Racket 的模块系统或 Racket/Scheme 宏的了解要好得多。
假设我想定义某种符号代数系统,并且我希望能够使用像 (+ a b)
这样的语法。当a
和 b
可能是cl:+
的事情coes不明白,比如多项式什么的。
好吧,我可以在 CL 中使用这样的包定义来做到这一点:
(defpackage :org.tfeb.symalg
(:use)
(:export "+" "-" "*" "/"))
(let ((p (find-package :org.tfeb.symalg)))
(do-external-symbols (s (find-package :cl))
(ecase (nth-value 1 (find-symbol (symbol-name s) p))
((nil)
(import s p)
(export s p))
((:external)
nil)
((:inherited :internal)
(error "package botch")))))
(defpackage :org.tfeb.symalg-user
(:use :org.tfeb.symalg))
(请注意,在现实生活中,您显然会编写一个宏以声明性且更灵活的方式执行上述操作:确实,互联网上的某个人编写了一个名为“管道”的系统来执行此操作,有一天他可能会再次发布它。)
这样做是为了创建一个包,
org.tfeb.symalg
就像 cl
除了一些具体的符号不同,还有一个包org.tfeb.symalg-user
它使用这个包而不是 cl
.在那个包中,(+ 1 2)
意味着 (org.tfeb.symalg:+ 1 2)
,但是 (car '(1 . 2))
意味着 (cl:car '(1 . 2))
.但
(defmethod foo (a)
(:method-combination +))
也意味着
(defmethod foo (a)
(:method-combination org.tfeb.symalg:+))
现在我遇到了一些麻烦:我想在任何地方使用符号
+
作为符号,我必须输入 cl:+
. (这个具体的例子很容易解决:我只需要为 org.tfeb.symalg:+
定义一个方法组合,我可能想在任何情况下都这样做,但还有其他情况。)这使得在我想使用作为语言一部分的名称(符号)作为符号的情况下,做这种“重新定义语言位”的事情很痛苦。
比较 Racket:这是 Racket 中的一个小模块定义,它提供(或实际上不提供)某些算术符号的变体版本):
#lang racket
(provide
(rename-out
(plus +)
(minus -)
(times *)
(divide /)))
(define (plus . args)
(apply + args))
...
(define plus-symbol '+)
这就是说,如果你使用这个模块,那么符号
+
的值是符号 plus
的值在模块中等等。但符号是同一个符号。如果您正在使用该模块,您可以轻松地检查这一点,例如 (eq? '+ plus-symbol)
,这将返回 #t
: 当你输入 +
时,你得到的符号没什么好笑的,这一切都在从这些符号到它们的值的映射中。所以这更好:如果 Racket 有一个 CLOS 风格的对象系统(它可能有大约六个,其中一些可能有一半工作),那么
+
方法组合会起作用,一般来说,任何关心符号的东西都会按照你想要的方式工作。除了您在 CL 中将符号作为符号进行操作的最常见的事情之一是宏。如果您尝试在 Racket 中对宏采用 CL 方法,那么到处都是头发。
在 CL 中,方法通常是这样的:
这一切都很好:如果我的宏是在
+
的情况下定义的意味着 org.tfeb.symalg:+
那么,如果它的扩展涉及+
真的涉及org.tfeb.symalg:+
,并且该包在编译时存在这么久(因为编译时和宏扩展时间会交织在一起),并且在运行时和定义在那里,那么一切都会好起来的。但它在 Racket 中不是一回事:如果我写一个宏,那么我知道符号
+
只是符号+
.但是什么+
根据模块状态,手段可能在不同时间完全不同。这可能意味着,例如,在编译时 +
意味着 Racket 的 native 加法函数,因此编译器可以将其优化到地面,但也许在运行时它意味着其他东西,因为现在模块状态不同。符号+
没有包含足够的信息来知道它应该意味着什么。如果您还考虑到 Scheme 人员真的很关心以一种干净的方式将这类事情整理出来的信息,并且肯定对 CL 对“停止思考并使用 gensyms”的宏的方法不满意,那么一切都会好吧’,Schemes 是 Lisp-1s 对这些都没有帮助,你可以看到这里有一些相当重要的问题需要解决。
关于clojure - 包 vs 命名空间 vs 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41321390/