Common Lisp和Scheme中的函数和变量是否有不同的命名空间?

31
Scheme使用单一命名空间来管理所有变量,无论它们是否绑定到函数或其他类型的值。Common Lisp将二者分开,因此标识符“hello”可能在一个上下文中指向函数,在另一个上下文中指向字符串。
(注1:这个问题需要以上内容的示例;请随意编辑并添加一个示例,或通过电子邮件将其发送给原作者,我会这样做。)
但是,在某些情况下,例如将函数作为参数传递给其他函数时,程序员必须显式区分他指定的是函数变量还是非函数变量,使用#',如下所示:
(sort (list '(9 A) '(3 B) '(4 C)) #'< :key #'first)
我一直认为这是一个小瑕疵,但最近我遇到了一个argument,认为这实际上是一个特性:
引用: ......重要的区别实际上在于形式的语法,而不在于对象的类型。在不知道涉及的运行时值的情况下,很明显函数形式的第一个元素必须是函数。CL利用这一事实,并将其与宏和特殊形式一起成为语言的一部分,这些形式也可以(并且必须)静态确定。那么我的问题是:当函数名称的主要用途是出现在不太可能出现变量名称的地方时,为什么要将函数名称和变量名称放在同一个命名空间中?
考虑类名的情况:为什么名为 FOO 的类会阻止使用名为 FOO 的变量?我只有在期望类名的上下文中才会用到名为 FOO 的类。如果极少数情况下需要获取绑定到类名 FOO 的类对象,则可以使用 FIND-CLASS。从我的经验来看,这个论点确实有些道理;Haskell 中也有类似的情况,字段名称也是用于访问字段的函数。这有点尴尬:
data Point = Point { x, y :: Double {- lots of other fields as well --} }
isOrigin p = (x p == 0) && (y p == 0)

这可以通过一些额外的语法来解决,特别是由NamedFieldPuns扩展提供的非常好的语法:

isOrigin2 Point{x,y} = (x == 0) && (y == 0)

那么,除了一致性之外,在Common Lisp和Scheme中,以及一般情况下,一个命名空间包含所有值与分开为函数和非函数值各自拥有命名空间的优缺点是什么?


1
“Lisp与Scheme”的说法有些不准确。Lisp是一系列编程语言,而Scheme是Lisp的一个方言。Common Lisp、Emacs Lisp、ISLisp、AutoLisp、Clojure等也是Lisp家族的成员。有几个Lisp方言具有单独的命名空间(用于函数和变量),而有几个则没有。例如,一种新的Lisp方言Clojure就没有单独的命名空间。而ISLisp(一种ISO标准化的Lisp方言)则有。 - Rainer Joswig
你说得对。我已经更新了帖子,使用“Common Lisp”而不是仅仅的“Lisp”。 - cjs
6个回答

24
这两种不同的方法有名称:Lisp-1和Lisp-2。 Lisp-1对变量和函数拥有一个命名空间(如Scheme),而Lisp-2对变量和函数有单独的命名空间(如Common Lisp)。我提到这一点是因为您可能不了解术语,因为您在问题中没有提及它。
维基百科引用此争论
“是否为函数提供单独的命名空间是Lisp社区的争议之源。通常称为Lisp-1与Lisp-2之争。 Lisp-1指Scheme的模型,而Lisp-2指Common Lisp的模型。这些名称是由Richard P. Gabriel和Kent Pitman在1988年的一篇论文中创造的,该论文广泛比较了这两种方法。”
Gabriel和Pitman的论文标题为Function Cells and Value Cells的分离技术问题正是针对这个问题。

12
实际上,正如Richard Gabriel和Kent Pitman的论文所述,这场辩论是关于Lisp-5对Lisp-6的较量,因为已经有几个其他命名空间存在,论文中提到了类型名称、标记名称、块名称和声明名称。 编辑:这似乎是不正确的,如Rainer在评论中指出的那样:Scheme实际上似乎是一个Lisp-1。以下内容基本上不受此错误影响。 无论符号表示要执行的内容还是要引用的内容,都可以从上下文中清楚地看出。将函数和变量放入同一命名空间主要是一种限制:程序员不能将相同的名称用于事物和操作。 Lisp-5从中获得的只是避免从与当前上下文意味着的命名空间不同的命名空间引用某些东西的某些语法开销。 编辑:这并不是全部,只是表面。 我知道Lisp-5支持者喜欢函数是数据的事实,并且这体现在语言核心中。我喜欢能够将列表称为“list”,将car称为“car”,而不会让编译器困惑,并且函数本质上是一种特殊的数据类型。 编辑:这是我的主要观点:单独的命名空间根本不是一个弊端。 我也喜欢Pascal Constanza所说的话

4
一般的讨论是关于Lisp-1和Lisp-n的比较。在Scheme中,一些东西是第一类对象并且有一个单一的命名机制。例如,在Common Lisp中用于非局部控制转移的标记、块名称等等,它们由标识符在其自己的命名空间中表示。Scheme使用延续作为第一类对象,而且没有它们自己的命名空间。我也更喜欢Lisp-n,但认识到Lisp-1背后的设计原则:使所有东西成为“第一类”值,并提供单一的命名机制。 - Rainer Joswig
嘿,我一直认为Scheme更接近Lisp,我不知道它没有标签和块。无论如何,我并不是要以任何方式推迟Scheme,只是表达我的偏好,并且在我看来,拥有单独的命名空间绝对不是一个缺点。 - Svante
我不太明白你所说的“上下文总是清晰”的意思。你是在说当我评估(map f l)时,它会使用f的函数命名空间绑定,而不是变量命名空间绑定吗?还是说我必须评估(map #'f l),这种情况下因为程序员必须显式消除歧义,所以从上下文中并不清楚呢? - cjs
1
S-表达式的第一个元素是要执行的内容,这默认情况下取自与该符号相关联的函数。所有后续元素都是要传递给执行内容的内容,默认情况下这些内容取自与相应符号相关联的值。如果你评估 (map f l),那么与 map 相关联的函数将被应用于与 fl 相关联的值,这是完全明确的。这里的“上下文”指的是“词法上下文”。"#'"并不能消除歧义,它只是覆盖了默认设置。 - Svante
1
小修正:第一个元素命名函数是正确的。但它不是从与符号关联的函数中取出。它是从函数命名空间中与名称在词法上相关联的函数中取出。如果在函数命名空间中没有词法绑定函数,则使用符号的函数值。#'foo或(FUNCTION foo)是访问函数命名空间并检索函数对象作为值的一种方式。如果在该名称下没有词法绑定函数,则返回符号的函数值。对于词法绑定的名称,不涉及任何SYMBOL。 - Rainer Joswig
感谢您澄清这个问题。 - Svante

2
我在Python和Ruby中遇到了类似的区别(Python使用统一命名空间,而Ruby则将方法和非方法分开命名空间)。在这种情况下,我更喜欢Python的方法。例如,在这种方法中,如果我想制作一个列表,其中有些是函数,而其他不是,我不需要根据它们的“功能性”来改变它们的名称。同样的考虑也适用于所有需要传递函数对象而不是调用它们的情况(高阶函数的参数和返回值等)。
非函数也可以被调用(如果它们的类定义了__call__,在Python中是一种特殊的“运算符重载”),因此“上下文区别”也不一定清晰。
然而,我的“lisp-oid”经验主要是Scheme而不是Common Lisp,所以我可能会受到熟悉统一命名空间的潜意识偏见的影响。

在Ruby中,您可以使用方法做更多或更少相同的事情:f = obj.method(:foo)将为您提供一个Method对象,这是一个完全正常的对象。您可以使用f.call(...)在对象的上下文中调用该方法。但这并不意味着在Ruby中函数是真正的一等公民,虽然我不想深入所有肮脏的细节。 - cjs

2
在Scheme中,函数的名称只是一个带有该函数值的变量。无论我使用(define x (y) (z y))还是(let ((x (lambda (y) (z y)))),我都定义了一个可以调用的函数。因此,在Scheme中,“变量名称很少会想要出现在那里”的想法在某种程度上是站不住脚的。
Scheme是一种具有特色的函数式语言,因此将函数视为数据是其原则之一。将函数作为一种自己的类型存储,就是延续这个思想的一种方式。

1

我认为,至少对于通用Lisp而言,最大的缺点就是可理解性。我们都可以一致认同,它为变量和函数使用不同的命名空间,但它到底有几个命名空间呢?在《PAIP》中,Norvig表明它至少有“七个”命名空间。

当语言的经典著作之一,由一位备受尊敬的程序员撰写的书籍,甚至无法在出版的书中确定这个问题时,我认为存在问题。我并不反对多个命名空间,但至少希望该语言足够简单,以至于任何人都能完全理解它的这一方面。

对于变量和函数使用相同的符号,我感到舒适,但在更隐晦的领域中,出于恐惧(命名空间冲突可能很难调试!),我会使用不同的名称,而这实际上不应该发生。


0

两种方法都有好处。然而,我发现当它很重要的时候,我更喜欢同时拥有一个函数列表和一个变量列表,而不是拼错其中一个。


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