斯卡拉2.8 breakOut
在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
的参数 ?
答案可以在map
的定义中map
:
def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That
请注意,它有两个参数。 第一个是你的function,第二个是隐含的。 如果你不提供这种隐含的,Scala会select最具特色的一个。
关于breakOut
那么, breakOut
的目的是breakOut
呢? 考虑给出的问题的例子,你需要一个string列表,将每个string转换成一个元组(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
的types已经被推断,因为我们知道map
需要CanBuildFrom[List[String], (Int, String), Map[Int, String]]
。 因此:
From = List[String] T = (Int, String) To = Map[Int, String]
总而言之,让我们来看看breakOut
本身所接收到的隐式。 它的types是CanBuildFrom[Nothing,T,To]
。 我们已经知道所有这些types,所以我们可以确定我们需要一个types为CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
的隐式CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
。 但是有没有这样的定义?
我们来看看CanBuildFrom
的定义:
trait CanBuildFrom[-From, -Elem, +To] extends AnyRef
所以CanBuildFrom
在它的第一个types参数上是contra-variant。 因为Nothing
是底层类(即它是一切的一个子类),这意味着任何类都可以用来代替Nothing
。
由于这样的构build器存在,Scala可以使用它来产生所需的输出。
关于build设者
Scala的集合库有很多方法,包括获取原始集合,以某种方式处理它(对于map
,转换每个元素),并将结果存储在一个新的集合中。
为了最大化代码重用,结果的存储是通过一个构build器 ( scala.collection.mutable.Builder
)来完成的,它基本上支持两个操作:追加元素并返回结果集合。 这个结果集合的types将取决于构build器的types。 因此, List
构build器将返回一个List
,一个Map
构build器将返回一个Map
,依此类推。 map
方法的实现不需要关心结果的types:构build器负责处理它。
另一方面,这意味着map
需要以某种方式接收这个构build器。 deviseScala 2.8 Collections时遇到的问题是如何select最好的build设者。 例如,如果我要写Map('a' -> 1).map(_.swap)
,我想要返回Map(1 -> 'a')
。 另一方面, Map('a' -> 1).map(_._1)
不能返回一个Map
(它返回一个Iterable
)。
通过这个CanBuildFrom
隐式来执行从已知types的expression式中产生尽可能好的Builder
的魔法。
关于CanBuildFrom
为了更好地解释发生了什么,我将给出一个示例,其中映射的集合是Map
而不是List
。 我稍后会回到List
。 现在,考虑这两个expression式:
Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length) Map(1 -> "one", 2 -> "two") map (_._2)
第一个返回一个Map
,第二个返回一个Iterable
。 返回适合的集合的魔力是CanBuildFrom
的工作。 让我们再次考虑map
的定义来理解它。
方法map
从TraversableLike
inheritance。 它在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
特征inheritance的,所以A
和Repr
可以从它们中inheritance。 最后一个得到的偏好,但。 因此,遵循不可变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]
的types参数一直传递到链中,则会发现传递给TraversableLike
的types,因此, map
使用的types是:
A = (Int,String) Repr = Map[Int, String]
回到这个例子,第一个映射是接收types为((Int, String)) => (Int, Int)
的函数,第二个映射是接收types为((Int, String)) => String
的函数。 我使用双括号来强调它是一个被接收的元组,正如我们所看到的那样。
有了这些信息,我们来考虑其他types。
map Function.tupled(_ -> _.length): B = (Int, Int) map (_._2): B = String
我们可以看到第一个map
返回的types是Map[Int,Int]
,第二个是Iterable[String]
。 看看map
的定义,很容易看出这些就是那个值。 但他们从哪里来?
如果我们查看所涉及的类的伴随对象,我们看到一些隐含的声明提供它们。 在对象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
提供工厂。
斯卡拉将select最具体的隐含可用。 在第一种情况下,它是第一个CanBuildFrom
。 在第二种情况下,由于第一种情况不匹配,所以select了第二种CanBuildFrom
。
回到问题
让我们来看看这个问题的代码, List
和map
的定义(再次)来看看这些types是如何被推断出来的:
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")
的types是List[String]
,所以在TraversableLike
上定义的typesA
和Repr
是:
A = String Repr = List[String]
(x => (x.length, x))
是(String) => (Int, String)
,所以B
的types是:
B = (Int, String)
最后一个未知types, That
是map
的结果的types,我们也已经有了:
val map : Map[Int,String] =
所以,
That = Map[Int, String]
这意味着breakOut
必须返回CanBuildFrom[List[String], (Int, String), Map[Int, String]]
的types或子types。
我想build立在丹尼尔的答案之上。 这是非常彻底的,但正如在评论中指出的那样,它并没有解释什么是突破。
从Re:支持明确的build设者 (2009-10-23),这里是我相信突破:
它给了编译器一个build议,隐式地select哪个Builder(本质上它允许编译器select它认为最适合情况的工厂)。
例如,请参阅以下内容:
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)
您可以看到编译器隐式select的返回types与预期的types最匹配。 根据你如何声明接收variables,你会得到不同的结果。
以下是指定构build器的等效方法。 注意在这种情况下,编译器会根据构build器的types推断出预期的types:
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)
Daniel Sobral的回答非常好,应该和Scala Collections (Scala编程的第25章)一起阅读。
我只想详细说明为什么叫做breakOut
:
为什么叫breakOut
?
因为我们想要打破一种types,
打破什么types进入什么types? 让我们看Seq
上的map
函数为例:
Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That
如果我们想直接从一个序列的元素映射来构build一个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只知道如何构build另一个Seq(即有一个隐含的CanBuildFrom[Seq[_], B, Seq[B]]
构build工厂可用,但从Seq到Map 没有构build工厂。
为了编译,我们需要以某种方式breakOut
types需求 ,并且能够构build一个为map
函数生成一个Map的构build器来使用。
正如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]]
typesCanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]
,而编译器能够find隐式的构build器工厂CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
来代替CanBuildFrom[Nothing, T, To]
,用于breakOut来创build实际的构build器。
请注意,在Map中定义了CanBuildFrom[Map[_, _], (A, B), Map[A, B]]
,并简单地启动一个使用底层Map的MapBuilder
。
希望这清除了事情。
一个简单的例子来了解breakOut
function:
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]