在 Scala 2.8 中,scala.collection.package.scala
中有一个对象:
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
我被告知这会导致:
> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
map: Map[Int,String] = Map(6 -> London, 5 -> Paris)
这里发生了什么?为什么 breakOut
被称为 作为我的 List
的参数?
List
的参数,而是 map
。
答案在 map
的定义中找到:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
请注意,它有两个参数。第一个是你的函数,第二个是隐式的。如果你不提供隐含的,Scala 将选择最具体的可用。
关于breakOut
那么,breakOut
的用途是什么?考虑为问题给出的示例,您获取一个字符串列表,将每个字符串转换为一个元组 (Int, String)
,然后从中生成一个 Map
。最明显的方法是生成一个中间 List[(Int, String)]
集合,然后对其进行转换。
鉴于 map
使用 Builder
来生成结果集合,难道不能跳过中间的 List
并将结果直接收集到 Map
中吗?显然,是的,是的。但是,为此,我们需要将正确的 CanBuildFrom
传递给 map
,而这正是 breakOut
所做的。
那么,让我们看看 breakOut
的定义:
def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply() ; def apply() = b.apply()
}
请注意,breakOut
是参数化的,它返回 CanBuildFrom
的实例。碰巧的是,类型 From
、T
和 To
已经被推断出来,因为我们知道 map
期待 CanBuildFrom[List[String], (Int, String), Map[Int, String]]
。所以:
From = List[String]
T = (Int, String)
To = Map[Int, String]
最后,让我们检查一下 breakOut
本身接收到的隐式。它是 CanBuildFrom[Nothing,T,To]
类型。我们已经知道所有这些类型,因此我们可以确定我们需要类型为 CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
的隐式。但是有这样的定义吗?
让我们看一下 CanBuildFrom
的定义:
trait CanBuildFrom[-From, -Elem, +To]
extends AnyRef
所以 CanBuildFrom
在它的第一个类型参数上是逆变的。因为 Nothing
是一个底层类(即它是一切的子类),这意味着 any 类可以用来代替 Nothing
。
由于存在这样的构建器,Scala 可以使用它来生成所需的输出。
关于建筑商
Scala 集合库中的许多方法包括获取原始集合,以某种方式对其进行处理(在 map
的情况下,转换每个元素),然后将结果存储在新集合中。
为了最大限度地重用代码,这种结果存储是通过 builder (scala.collection.mutable.Builder
) 完成的,它基本上支持两种操作:添加元素和返回结果集合。此结果集合的类型将取决于构建器的类型。因此,List
构建器将返回 List
,Map
构建器将返回 Map
,依此类推。 map
方法的实现不需要关心结果的类型:构建器会处理它。
另一方面,这意味着 map
需要以某种方式接收此构建器。设计 Scala 2.8 Collections 时面临的问题是如何选择最好的构建器。例如,如果我要写 Map('a' -> 1).map(_.swap)
,我希望得到一个 Map(1 -> 'a')
。另一方面,Map('a' -> 1).map(_._1)
不能返回 Map
(它返回 Iterable
)。
从已知类型的表达式中产生最好的 Builder
的魔法是通过这个 CanBuildFrom
隐式执行的。
关于CanBuildFrom
为了更好地解释发生了什么,我将给出一个示例,其中被映射的集合是 Map
而不是 List
。稍后我会回到 List
。现在,考虑以下两个表达式:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)
第一个返回 Map
,第二个返回 Iterable
。返回一个合适的集合的魔力是 CanBuildFrom
的工作。让我们再次考虑 map
的定义来理解它。
方法 map
继承自 TraversableLike
。它在 B
和 That
上进行参数化,并利用类型参数 A
和 Repr
来参数化类。让我们一起看看这两个定义:
TraversableLike
类定义为:
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
要了解 A
和 Repr
的来源,让我们考虑 Map
本身的定义:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
因为 TraversableLike
由扩展 Map
的所有特征继承,所以 A
和 Repr
可以从它们中的任何一个继承。不过,最后一个获得了偏好。因此,根据不可变 Map
的定义以及将其连接到 TraversableLike
的所有特征,我们有:
trait Map[A, +B]
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends MapLike[A, B, This]
trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
如果您将 Map[Int, String]
的类型参数一路向下传递,我们发现传递给 TraversableLike
并因此被 map
使用的类型是:
A = (Int,String)
Repr = Map[Int, String]
回到示例,第一个映射接收类型为 ((Int, String)) => (Int, Int)
的函数,第二个映射接收类型为 ((Int, String)) => String
的函数。我使用双括号来强调它是一个正在接收的元组,因为这是我们看到的 A
的类型。
有了这些信息,让我们考虑其他类型。
map Function.tupled(_ -> _.length):
B = (Int, Int)
map (_._2):
B = String
我们可以看到第一个map
返回的类型是Map[Int,Int]
,第二个是Iterable[String]
。查看 map
的定义,很容易看出这些是 That
的值。但它们来自哪里?
如果我们查看所涉及类的伴生对象,我们会看到一些提供它们的隐式声明。在对象 Map
上:
implicit def canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]
在对象 Iterable
上,其类由 Map
扩展:
implicit def canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]
这些定义为参数化 CanBuildFrom
提供工厂。
Scala 将选择最具体的可用隐式。在第一种情况下,它是第一个 CanBuildFrom
。在第二种情况下,由于第一个不匹配,它选择了第二个 CanBuildFrom
。
回到问题
让我们看看问题的代码、List
和 map
的定义(再次),看看如何推断类型:
val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)
sealed abstract class List[+A]
extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]]
trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]]
extends SeqLike[A, Repr]
trait SeqLike[+A, +Repr]
extends IterableLike[A, Repr]
trait IterableLike[+A, +Repr]
extends Equals with TraversableLike[A, Repr]
trait TraversableLike[+A, +Repr]
extends HasNewBuilder[A, Repr] with AnyRef
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
List("London", "Paris")
的类型是 List[String]
,因此在 TraversableLike
上定义的类型 A
和 Repr
是:
A = String
Repr = List[String]
(x => (x.length, x))
的类型是 (String) => (Int, String)
,所以 B
的类型是:
B = (Int, String)
最后一个未知类型 That
是 map
的结果类型,我们也已经有了它:
val map : Map[Int,String] =
所以,
That = Map[Int, String]
这意味着 breakOut
必须返回 CanBuildFrom[List[String], (Int, String), Map[Int, String]]
的类型或子类型。
我想以丹尼尔的回答为基础。它非常彻底,但正如评论中所述,它没有解释突破的作用。
取自 Re: Support for explicit Builders (2009-10-23),以下是我认为 breakout 的作用:
它给编译器一个关于隐式选择哪个 Builder 的建议(本质上它允许编译器选择它认为最适合情况的工厂。)
例如,请参阅以下内容:
scala> import scala.collection.generic._
import scala.collection.generic._
scala> import scala.collection._
import scala.collection._
scala> import scala.collection.mutable._
import scala.collection.mutable._
scala>
scala> def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
| new CanBuildFrom[From, T, To] {
| def apply(from: From) = b.apply() ; def apply() = b.apply()
| }
breakOut: [From, T, To]
| (implicit b: scala.collection.generic.CanBuildFrom[Nothing,T,To])
| java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)
scala> val imp = l.map(_ + 1)(breakOut)
imp: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4)
scala> val arr: Array[Int] = l.map(_ + 1)(breakOut)
imp: Array[Int] = Array(2, 3, 4)
scala> val stream: Stream[Int] = l.map(_ + 1)(breakOut)
stream: Stream[Int] = Stream(2, ?)
scala> val seq: Seq[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Seq[Int] = ArrayBuffer(2, 3, 4)
scala> val set: Set[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Set[Int] = Set(2, 4, 3)
scala> val hashSet: HashSet[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.HashSet[Int] = Set(2, 4, 3)
您可以看到编译器隐式选择返回类型以最好地匹配预期类型。根据您声明接收变量的方式,您会得到不同的结果。
以下将是指定构建器的等效方法。请注意,在这种情况下,编译器将根据构建器的类型推断出预期的类型:
scala> def buildWith[From, T, To](b : Builder[T, To]) =
| new CanBuildFrom[From, T, To] {
| def apply(from: From) = b ; def apply() = b
| }
buildWith: [From, T, To]
| (b: scala.collection.mutable.Builder[T,To])
| java.lang.Object with
| scala.collection.generic.CanBuildFrom[From,T,To]
scala> val a = l.map(_ + 1)(buildWith(Array.newBuilder[Int]))
a: Array[Int] = Array(2, 3, 4)
breakOut
”?我在想像 convert
或 buildADifferentTypeOfCollection
(但更短)之类的东西可能更容易记住。
Daniel Sobral 的回答很棒,应该与 Architecture of Scala Collections 一起阅读(Scala 编程的第 25 章)。
我只是想详细说明为什么它被称为 breakOut
:
为什么叫breakOut?
因为我们想从一种类型突破到另一种类型:
突破什么类型变成什么类型?让我们以 Seq
上的 map
函数为例:
Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
如果我们想直接通过对序列元素的映射来构建 Map,例如:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))
编译器会抱怨:
error: type mismatch;
found : Seq[(String, Int)]
required: Map[String,Int]
原因是 Seq 只知道如何构建另一个 Seq(即有一个隐式的 CanBuildFrom[Seq[_], B, Seq[B]]
构建器工厂可用,但是从 Seq 到 Map 没有 NO 构建器工厂)。
为了编译,我们需要以某种方式 breakOut
的类型要求,并且能够构造一个生成器生成 Map 供 map
函数使用.
正如 Daniel 所解释的,breakOut 具有以下签名:
def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To] =
// can't just return b because the argument to apply could be cast to From in b
new CanBuildFrom[From, T, To] {
def apply(from: From) = b.apply()
def apply() = b.apply()
}
Nothing
是所有类的子类,因此任何构建器工厂都可以代替 implicit b: CanBuildFrom[Nothing, T, To]
。如果我们使用 breakOut 函数提供隐式参数:
val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)
它将编译,因为 breakOut
能够提供所需的 CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
类型,而编译器能够找到类型为 CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
的隐式构建器工厂来代替 CanBuildFrom[Nothing, T, To]
,以便 breakOut 用于创建实际的建造者。
请注意,CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
是在 Map 中定义的,它只是启动一个使用底层 Map 的 MapBuilder
。
希望这可以解决问题。
了解 breakOut
作用的简单示例:
scala> import collection.breakOut
import collection.breakOut
scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> set.map(_ % 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 0)
scala> val seq:Seq[Int] = set.map(_ % 2)(breakOut)
seq: Seq[Int] = Vector(1, 0, 1, 0) // map created a Seq[Int] instead of the default Set[Int]
val seq:Seq[Int] = set.map(_ % 2).toVector
不会为您提供重复的值,因为 Set
是为 map
保留的。
set.map(_ % 2)
首先创建一个 Set(1, 0)
,然后将其转换为 Vector(1, 0)
。