14

Martining Odersky 在他的书中写道:

::,“construct”的发音为“cons”,表示非空列表。

列表构造方法 :: 和 ::: 是特殊的。因为它们以冒号结尾,所以它们被绑定到它们的右操作数。也就是说,诸如 x :: xs 之类的操作被视为方法调用 xs.::(x),而不是 x.::(xs)。事实上,x.::(xs) 没有意义,因为 x 是列表元素类型,它可以是任意的,所以我们不能假设这种类型会有 ::方法。出于这个原因, ::方法应该接受一个元素值并产生一个新列表

那么是::方法还是类?

4

2 回答 2

19

它既是一个又是一个方法。Cons 是一个类型参数化的类。List[A] 有两个子类:Cons 和 Nil。由于 Cons 是一个类,它可以由其构造函数创建,如下所示:

val s = new ::[Int](4, Nil)

Cons 是一个案例类,我们在进行模式匹配时使用构造函数。Cons 也是列表类上的一个方法,在其两个子类中实现。因此,我们可以在上面创建的 cons 类上使用 cons 方法。

val s1 = s.::(5)

可能会出现部分混淆,因为我们通常使用 List 对象的 apply 方法创建 List:

val s2 = List(1, 2, 3)

通常,对象的 apply 方法返回一个与对象同名的类的新实例。然而,这只是惯例。在这种特殊情况下,列表对象返回 Cons 子类的新实例。List 类本身是一个密封的抽象类,因此无法实例化。所以上面的 apply 方法做了以下事情:

val s2 = 1 :: 2 :: 3 :: Nil

任何以 ':' 结尾的方法都是其右侧操作数上的方法,因此可以将其重写为

val s2 = 1 :: (2 :: (3 :: Nil)) //or as
val s2 = Nil.::(3).::(2).::(1)

因此,Nil 对象上的 Cons(::) 方法将 3 作为参数并生成 Cons 类的匿名实例,其中 3 作为其头部,对 Nil 对象的引用作为其尾部。让我们将此匿名对象称为 c1。然后在 c1 上调用 Cons 方法,将 2 作为其参数返回一个新的匿名 Cons 实例化,我们将其称为 c2,其头部为 2,尾部为对 c1 的引用。最后,c2 对象上的 cons 方法将 1 作为参数,并返回命名对象 s2,其中 1 作为其头部,对 c2 的引用作为其尾部。

第二个混淆点是 REPL 和 Scala 工作表使用类的 toString 方法来显示结果。所以工作表给了我们:

val s3 = List(5, 6, 7)     // s3  : List[Int] = List(5, 6, 7)
val s4 = List()            // s4  : List[Nothing] = List()
val s5: List[Int] = List() // s5  : List[Int] = List()
s3.getClass.getName        // res3: String = scala.collection.immutable.$colon$colon
s4.getClass.getName        // res4: String = scala.collection.immutable.Nil$
s5.getClass.getName        // res5: String = scala.collection.immutable.Nil$

如上所述 List 是密封的,因此无法创建新的子子类,因为 Nil 是一个对象,而 Cons 是最终的。由于 Nil 是一个对象,因此无法对其进行参数化。Nil 继承自 List[Nothing]。乍一看这听起来没什么用,但请记住这些数据结构是不可变的,所以我们永远不能直接添加它们,Nothing 是每个其他类的子类。所以我们可以毫无问题地将 Nil 类(间接地)添加到任何 List 中。Cons 类有两个成员,一个是 head,另一个是 List。当您计时时,它是一个相当简洁的解决方案。

我不确定这是否有任何实际用途,但您可以使用 Cons 作为类型:

var list: ::[Int] = List (1,2,3).asInstanceOf[::[Int]]
println("Initialised List")
val list1 = Nil
list = list1.asInstanceOf[::[Int]] //this will throw a java class cast exception at run time
println("Reset List")
于 2013-10-17T12:30:19.520 回答
5

简短的回答:两者都有。

list 有一个名为 的子类::,但您不会经常明确地引用它。

当您编写 eg1 :: 2 :: Nil时, the::是on的一个方法,它在幕后List创建类的实例。::

::不过,最好不要将其视为方法或类,而应将其视为代数数据类型(ADT) 意义上的构造函数。Wikipedia 将 ADT 构造函数称为“准功能实体 [ies]”,这使得它们听起来比实际更复杂,但不一定是思考它们的坏方法。

List有两个构造函数, (在某些语言中::称为consNil )和. 我在上面链接的 Wikipedia 文章很好地介绍了列表作为代数数据类型的概念。

值得注意的是,在某些语言(如 Haskell)中,ADT 构造函数没有与它们相关联的自己的类型——它们只是创建 ADT 实例的函数。这通常在 Scala 中也是有效的,在这种情况下,引用 ADT 构造函数的类型(如::. 不过有可能——我们可以这样写:

def foo(xs: ::[Int]) = ???

或者这个(这里SomeOptionADT 的构造函数之一)。

def bar(s: Some[Int]) = ???

但这通常不是很有用,并且可以被认为是 Scala 实现代数数据类型的方式的产物。

于 2013-10-17T12:42:36.387 回答