如果Nothing类型处于类层次结构的底部,为什么我不能在其上调用任何可想象的方法?

16

Scala中的类型Nothing代表了(就我理解的)类型层次结构的底部,也可用符号⊥表示。也就是说,Nothing是任何给定类型的子类型。需要注意的是,James Iry详细解释了对于我们这些没有类型理论背景的人来说什么是Nothing类型!

我的问题是,既然Nothing是每种类型的子类型,为什么我不能在Nothing上调用任何类型的方法呢?显然,我不能对Nothing进行实例化,但是以下代码为什么无法编译通过?

var n: Nothing = _

def main(args: Array[String]) {
  println(n.length) //compile error: value length is not a member of Nothing
}

毫无疑问,由于NothingString的子类型,所以这应该没问题吧?请注意,下面的代码可以很好地编译通过!

var n: Nothing = _

def foo(s: String) : Int =  s.length

def main(args: Array[String]) {
  println(foo(n))
}

as does:

翻译为:

同样如此:

def main(args: Array[String]) {
  println(n.asInstanceOf[String].length) 
}

另一个需要考虑的因素是,在 n.length 中,Scala 不知道你正在谈论哪个方法 length -- 而且在你考虑它的参数(或 this 参数)是否具有正确类型之前,你需要知道这一点。在 foo(n) 中,已经很清楚了。 - Owen
3个回答

18

尽管Nothing是所有类型的子类型,但它除了Any中的方法外,不继承任何其他方法。这是因为Nothing更加适用于语言的函数式端。

这里的区别对于那些来自面向对象编程背景的人来说有点奇怪,但事实是,子类型作为一个概念与OOP非常不同。当然,面向对象真正意味着某种形式的子类型,但反过来并不成立。 Benjamin Pierce的《类型和编程语言》做了一个很好的工作,展示了一种具有子类型(但不是OO)的最小化语言F_<

现在,虽然如此,我确实同意Nothing免受常规继承规则的影响似乎有点不一致。 不过,从理论上讲,这完全有道理。


2

我认为Nothing可以接受任何方法,并对所有方法执行标准操作(抛出异常)。但是这并没有什么用。

通过显示编译错误,编译器警告程序员在代码的某个点上推断出了他最可能不想要的类型Nothing


但是我可以在空对象上调用 toString,我只是想知道是否有一些丑陋的东西从编译器的角度将 Nothing 视为特殊情况。或者我的类型理论是错误的,类型支持的方法和类型本身不一定相关。 - oxbow_lakes
我想这个想法是尽可能地进行编译时验证。当你得到一个编译时错误而不是运行时错误时,这并不丑陋,反而是好的。 - P Shved

0

你可以在 Nothing 变量上调用 toString 方法,这是因为它的定义如下:
final trait Nothing extends Any
而且 toString 是 Any 的成员方法。我认为 Scala 编译器只将 Nothing 视为类型边界,并将其在所有情况下视为与其他特征相同的特质。我觉得在类型为 Nothing 的变量上调用任何方法会非常奇怪。


但仍然有一些特殊处理正在进行。从Nothing签名类型中,我无法判断它是否扩展了每个其他类型。 - oxbow_lakes
1
啊,原来这是一个特质(trait),而不是一个类(class)。因此,你只能调用由特质显式定义的方法。这可以解释很多问题。 - jqno
1
@oxbow_lakes:规范中描述了特殊处理,因此编译器在类型边界方面对其进行特殊处理,例如对于每种类型T,以下内容是正确的:Nothing < T < Any,但除此之外,为什么编译器应该将Nothing trait视为更加特殊?规范中没有提到这一点。 - Nikolay Ivanov

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