首页 文章

Scala 2.8 breakOut

提问于
浏览
217

在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 的参数?

4 回答

  • 317

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

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

    请注意,它有两个参数 . 第一个是你的功能,第二个是隐含的 . 如果您不提供隐式,Scala将选择最具体的可用 .

    About 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 的实例 . 碰巧,类型 FromTTo 已被推断,因为我们知道 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可以使用它来产生所需的输出 .

    About Builders

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

    为了最大化代码重用,结果的存储是通过构建器( scala.collection.mutable.Builder )完成的,它基本上支持两个操作:追加元素,并返回结果集合 . 此结果集合的类型取决于构建器的类型 . 因此, List 构建器将返回 ListMap 构建器将返回 Map ,依此类推 . map 方法的实现不需要关注结果的类型:构建器负责处理它 .

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

    从已知类型的表达式中产生尽可能最好的 Builder 的魔力是通过这个 CanBuildFrom 隐式执行的 .

    About CanBuildFrom

    为了更好地解释's going on, I'll给出一个示例,其中映射的集合是 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 . 它在 BThat 上进行参数化,并使用参数化类的类型参数 ARepr . 让我们一起看两个定义:

    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 由所有延伸 Map 的特征继承,所以 ARepr 可以从其中任何特征继承 . 不过,最后一个得到了偏好 . 因此,遵循不可变 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]
    

    回到示例,第一个 Map 正在接收类型为 ((Int, String)) => (Int, Int) 的函数,第二个 Map 正在接收类型为 ((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 .

    Back to the Question

    让我们看一下问题的代码, Listmap 的定义(再次)看看如何推断类型:

    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 上定义的类型 ARepr 是:

    A = String
    Repr = List[String]
    

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

    B = (Int, String)
    

    最后一个未知类型, Thatmap 的结果类型,我们也已经有了:

    val map : Map[Int,String] =
    

    所以,

    That = Map[Int, String]
    

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

  • 6

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

    摘自Re: Support for explicit Builders(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)
    
  • 85

    Daniel Sobral的答案很棒,应该与Architecture of Scala Collections(Scala编程的第25章)一起阅读 .

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

    为什么叫做breakOut?

    因为我们想 break out of one type and into another

    突破什么类型成什么类型?让我们看一下 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 of the type requirement ,并能够构造一个构建器,为 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 .

    希望这可以解决问题 .

  • 3

    一个简单的例子来理解 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]
    

相关问题