在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作为列表的参数?


答案可以在map的定义中找到:

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

注意,它有两个参数。第一个是函数,第二个是隐式函数。如果您不提供隐式的,Scala将选择最特定的可用选项。

关于突破

那么,breakOut的目的是什么?考虑这个问题给出的例子,你获取一个字符串列表,将每个字符串转换为一个元组(Int, string),然后从中生成一个Map。最明显的方法是生成一个中间的List[(Int, String)]集合,然后转换它。

假设映射使用Builder来生成结果集合,那么是否可以跳过中间列表并将结果直接收集到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是一个底层类(即,它是所有东西的子类),这意味着任何类都可以用来代替Nothing。

由于存在这样的构建器,Scala可以使用它来生成所需的输出。

对建筑商

Scala集合库中的许多方法包括获取原始集合,以某种方式处理它(在map的情况下,转换每个元素),并将结果存储在一个新的集合中。

为了最大化代码重用,这种结果存储是通过构建器完成的,它基本上支持两种操作:追加元素和返回结果集合。此结果集合的类型将取决于构建器的类型。因此,List构建器将返回List, Map构建器将返回Map,等等。map方法的实现本身不需要关心结果的类型:构建器会处理它。

另一方面,这意味着映射需要以某种方式接收这个构建器。设计Scala 2.8 Collections时面临的问题是如何选择最好的构建器。例如,如果我要写Map('a' -> 1). Map(_.swap),我想要得到一个Map(1 -> 'a')回来。另一方面,Map('a' -> 1). Map(_._1)不能返回一个Map(它返回一个Iterable)。

从已知的表达式类型生成最好的生成器的神奇之处是通过这个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的定义来理解它。

方法映射继承自traversableelike。它在B和That上进行参数化,并使用类型参数A和Repr,它们对类进行参数化。让我们一起看看这两个定义:

类traversabelike定义为:

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]]

因为遍历类是由所有扩展Map的特征继承的,A和Repr可以从它们中的任何一个继承。不过,最后一种会得到优先考虑。因此,按照不可变Map的定义和所有连接到可穿越类的特征,我们有:

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]的类型参数一直传递下去,我们会发现传递给traversabelike的类型,因此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的值。但它们从何而来?

如果我们查看所涉及类的伴生对象,我们会看到一些隐式声明提供了它们。在对象地图上:

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's和map's的定义(再次),看看类型是如何推断的:

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],所以在traversable elike上定义的类型A和Repr是:

A = String
Repr = List[String]

(x => (x.length, x))的类型为(String) => (Int, String),因此B的类型为:

B = (Int, String)

最后一个未知类型,这是map结果的类型,我们已经有了:

val map : Map[Int,String] =

So,

That = Map[Int, String]

这意味着breakOut必须返回CanBuildFrom[List[String], (Int, String), Map[Int, String]]的类型或子类型。


我想以丹尼尔的回答为基础。它非常彻底,但正如评论中所指出的,它并没有解释breakout的功能。

摘自Re:对显式构建器的支持(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的功能:

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]

Daniel Sobral的回答非常棒,应该和Scala集合的架构(Scala编程第25章)一起阅读。

我只是想详细说明一下为什么它被称为breakOut:

为什么叫breakOut?

因为我们想从一种类型突破到另一种类型:

从哪种类型突破到哪种类型?让我们以Seq上的地图功能为例:

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的构建器工厂)。

为了进行编译,我们需要以某种方式对类型需求进行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是所有类的子类,所以任何构建器工厂都可以代替隐式的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。

希望这能把事情弄清楚。