Scala列表连接,:::vs ++

在Scala中连接列表的:::++之间有什么区别吗?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)


scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)


scala> res0 == res1
res2: Boolean = true

的文档来看,++更通用,而:::是__abc2特有的。提供后者是因为它在其他函数式语言中使用吗?

196693 次浏览

的遗产。List最初被定义为函数式语言外观:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists


list match {
case head :: tail => "non-empty"
case Nil          => "empty"
}

当然,Scala以一种特别的方式发展了其他集合。当2.8发布时,集合被重新设计,以实现最大限度的代码重用和一致的API,这样你就可以使用++来连接任何两个集合——甚至迭代器。然而,List必须保留它原来的操作符,除了一两个已弃用的操作符。

:::仅适用于列表,而++可用于任何可遍历对象。在当前实现(2.9.0)中,如果实参也是List++将回退到:::

不同的是,第一句被解析为:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

而第二个例子被解析为:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

因此,如果您正在使用宏,您应该小心。

此外,用于两个列表的++调用:::,但开销更大,因为它要求一个隐式值来拥有从列表到列表的构建器。但是微基准测试在这个意义上并没有证明任何有用的东西,我猜编译器优化了这样的调用。

热身后的微基准测试。

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100


scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

正如Daniel C. Sobrai所说,可以使用++将任何集合的内容附加到列表中,而使用:::只能连接列表。

始终使用:::。有两个原因:效率和类型安全。

效率

x ::: y ::: zx ++ y ++ z快,因为:::是右结合的。x ::: y ::: z被解析为x ::: (y ::: z),这在算法上比(x ::: y) ::: z快(后者需要O(|x|)更多的步骤)。

类型安全

使用:::,你只能连接两个__abc1。使用++,你可以将任何集合附加到List,这很糟糕:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++也很容易与+混淆:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab