Scala 2.8突破


225

在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


13
简单的答案是,它不是对的争论List,而是对的争论map
Daniel C. Sobral

Answers:


325

答案在以下定义中找到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使用a Builder来产生结果集合,是否有可能跳过中介List并将结果直接收集到Map?中?显然,是的。但是,为此,我们需要将适当的参数传递CanBuildFrommap,而这正是这样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。碰巧的类型FromT并且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]。我们已经知道所有这些类型,因此可以确定我们需要type的隐式类型CanBuildFrom[Nothing,(Int,String),Map[Int,String]]。但是有这样的定义吗?

让我们看一下CanBuildFrom的定义:

trait CanBuildFrom[-From, -Elem, +To] 
extends AnyRef

因此CanBuildFrom其第一类型参数是反变量。因为Nothing是底层类(即它是所有内容的子类),所以可以使用任何类代替Nothing

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

关于建筑商

Scala集合库中的许多方法包括获取原始集合,以某种方式对其进行处理(对于map,转换每个元素)以及将结果存储在新集合中。

为了最大程度地重复使用代码,结果的存储是通过构建器scala.collection.mutable.Builder)完成的,该构建器基本上支持两种操作:附加元素和返回结果集合。此结果集合的类型将取决于构建器的类型。因此,List构建器将返回ListMap构建器将返回Map,依此类推。该map方法的实现不需要关心结果的类型:构建器会处理它。

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

Builder通过这种CanBuildFrom隐式执行从表达式的已知类型中产生最佳结果的魔力。

关于 CanBuildFrom

为了更好地说明发生了什么,我将举一个示例,其中要映射的集合是a Map而不是List。我待会再回头List。现在,请考虑以下两个表达式:

Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)

第一个返回a Map,第二个返回a 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 

要了解其来源ARepr来源,请考虑其Map自身的定义:

trait Map[A, +B] 
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]

因为TraversableLike是被所有扩展特性继承的MapA并且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

我们可以看到第一个返回的类型mapMap[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

回到问题

让我们再看一遍问题的代码Lists和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],因此on 的类型ARepr定义TraversableLike为:

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


61
丹尼尔(Daniel),我可以仔细检查您答案中的类型,但是一旦结束,我感觉自己并没有获得任何高水平的理解。什么突破?“ breakOut”这个名字是从哪里来的(我是从哪里来的)?为什么在这种情况下需要地图才能发布?当然,有某种方法可以简要回答这些问题吗?(即使为了掌握每个细节,仍然需要进行冗长的类型
划分

3
@Seth这是一个有效的问题,但是我不确定自己是否能胜任这项工作。可以在这里找到它的起源:article.gmane.org/gmane.comp.lang.scala.internals/1812/…。我会考虑的,但是现在,我想不出什么改进的方法。
Daniel C. Sobral

2
有没有一种方法可以避免指定Map [Int,String]的整个结果类型,而是可以编写类似以下内容的内容:'val map = List(“ London”,“ Paris”)。map(x =>(x。长度,x))(breakOut [... Map])'
IttayD 2010年

9
@SethTisue从我对这一解释的阅读中,似乎需要BreakOut来“突破”构建器需要从List [String]进行构建的要求。编译器需要您无法提供的CanBuildFrom [List [String],(Int,String),Map [Int,String]]。breakOut函数通过将CanBuildFrom中的第一个类型参数设置为Nothing来破坏它。现在,您只需要提供CanBuildFrom [Nothing,(Int,String),Map [Int,String]]。这很容易,因为它是由Map类提供的。
标记

2
@Mark当我找到breakOut时,我看到它解决的问题是monad坚持将映射(通过bind / flatMap)映射到自己的类型的方式。它允许人们使用一个monad将其“打破”为另一种monad类型。我不知道那是不是阿德里亚·摩尔(作者)想到的那样!
Ed Staub 2013年

86

我想以丹尼尔的回答为基础。这非常彻底,但是正如评论中指出的那样,它没有解释突破的作用。

摘自Re:对显式生成器的支持(2009-10-23),我相信突破的作用是:

它为编译器提供了有关隐式选择哪个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)

1
我不知道为什么将其命名为“ breakOut”?我在想类似convertbuildADifferentTypeOfCollection(但更短)的内容可能更容易记住。
KajMagnus

8

Daniel Sobral的回答很棒,应该与Scala Collections Architecture一起阅读编程第25章)一起阅读。

我只是想详细说明为什么叫它 breakOut

为什么叫 breakOut

因为我们想突破一种类型而变成另一种类型

突破什么类型变成什么类型​​?让我们来看一个map函数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是所有类的子类,因此可以替换任何构建器工厂implicit b: CanBuildFrom[Nothing, T, To]。如果我们使用breakOut函数提供隐式参数:

val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)

它将进行编译,因为breakOut能够提供所需的type CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]],而编译器能够找到type的隐式生成器工厂来CanBuildFrom[Map[_, _], (A, B), Map[A, B]]代替CanBuildFrom[Nothing, T, To],供breakOut用于创建实际的生成器。

请注意,这CanBuildFrom[Map[_, _], (A, B), Map[A, B]]是在Map中定义的,并且仅启动MapBuilder使用基础Map的。

希望这可以清除一切。


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]

谢谢你的例子!也val seq:Seq[Int] = set.map(_ % 2).toVector不会为您提供重复的值,因为Set为保留了map
马修·皮克林 Matthew Pickering)

@MatthewPickering正确!set.map(_ % 2)创建一个Set(1, 0)第一个,然后将其转换为Vector(1, 0)
fdietze
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.