在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 回答
答案可以在
map
的定义中找到:请注意,它有两个参数 . 第一个是你的功能,第二个是隐含的 . 如果您不提供隐式,Scala将选择最具体的可用 .
About breakOut
那么
breakOut
的目的是什么?考虑给出问题的示例,您获取字符串列表,将每个字符串转换为元组(Int, String)
,然后从中生成Map
. 最明显的方法是生成一个中间List[(Int, String)]
集合,然后转换它 .鉴于
map
使用Builder
生成结果集合,是否有可能跳过中间人List
并将结果直接收集到Map
?显然,是的,确实如此 . 但是,要做到这一点,我们需要将CanBuildFrom
传递给map
,这正是breakOut
所做的 .那么,让我们看一下
breakOut
的定义:请注意
breakOut
已参数化,并返回CanBuildFrom
的实例 . 碰巧,类型From
,T
和To
已被推断,因为我们知道map
正在期待CanBuildFrom[List[String], (Int, String), Map[Int, String]]
. 因此:总结一下,让我们来看看
breakOut
本身收到的隐含 . 它的类型为CanBuildFrom[Nothing,T,To]
. 我们已经知道所有这些类型,因此我们可以确定我们需要隐式类型CanBuildFrom[Nothing,(Int,String),Map[Int,String]]
. 但是有这样的定义吗?让我们来看看
CanBuildFrom
的定义:所以
CanBuildFrom
在其第一个类型参数上是反变量的 . 因为Nothing
是底层类(即,它是所有东西的子类),这意味着可以使用任何类来代替Nothing
.由于存在这样的构建器,Scala可以使用它来产生所需的输出 .
About Builders
Scala的集合库中的许多方法包括获取原始集合,以某种方式处理它(在
map
的情况下,转换每个元素),并将结果存储在新集合中 .为了最大化代码重用,结果的存储是通过构建器(
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
隐式执行的 .About CanBuildFrom
为了更好地解释's going on, I'll给出一个示例,其中映射的集合是
Map
而不是List
. 我稍后会回到List
. 现在,请考虑以下两个表达式:第一个返回
Map
,第二个返回Iterable
. 返回合适的收藏品的魔力是CanBuildFrom
的作品 . 让我们再次考虑map
的定义来理解它 .方法
map
继承自TraversableLike
. 它在B
和That
上进行参数化,并使用参数化类的类型参数A
和Repr
. 让我们一起看两个定义:类
TraversableLike
定义为:要了解
A
和Repr
的来源,让我们考虑一下Map
本身的定义:因为
TraversableLike
由所有延伸Map
的特征继承,所以A
和Repr
可以从其中任何特征继承 . 不过,最后一个得到了偏好 . 因此,遵循不可变Map
的定义以及将其连接到TraversableLike
的所有特征,我们有:如果你一直向下传递
Map[Int, String]
的类型参数,我们发现传递给TraversableLike
的类型,因此map
使用的类型是:回到示例,第一个 Map 正在接收类型为
((Int, String)) => (Int, Int)
的函数,第二个 Map 正在接收类型为((Int, String)) => String
的函数 . 我使用双括号来强调它是一个被接收的元组,因为我们看到的是A
的类型 .有了这些信息,我们考虑其他类型 .
我们可以看到第一个
map
返回的类型是Map[Int,Int]
,第二个是Iterable[String]
. 看看map
的定义,它很容易看出这些是That
的值 . 但它们来自哪里?如果我们查看所涉及的类的伴随对象,我们会看到一些提供它们的隐式声明 . 对象
Map
:对象
Iterable
,其类扩展为Map
:这些定义为参数化
CanBuildFrom
提供工厂 .Scala将选择最具体的隐式可用 . 在第一种情况下,它是第一个
CanBuildFrom
. 在第二种情况下,由于第一种情况不匹配,它选择了第二种CanBuildFrom
.Back to the Question
让我们看一下问题的代码,
List
和map
的定义(再次)看看如何推断类型:List("London", "Paris")
的类型是List[String]
,因此TraversableLike
上定义的类型A
和Repr
是:(x => (x.length, x))
的类型是(String) => (Int, String)
,因此B
的类型是:最后一个未知类型,
That
是map
的结果类型,我们也已经有了:所以,
这意味着
breakOut
必须返回CanBuildFrom[List[String], (Int, String), Map[Int, String]]
的类型或子类型 .我想以丹尼尔的答案为基础 . 这是非常彻底的,但正如评论中所指出的,它并没有解释突破的作用 .
摘自Re: Support for explicit Builders(2009-10-23),这是我认为突破的作用:
它为编译器提供了一个关于隐式选择哪个Builder的建议(基本上它允许编译器选择它认为最符合情况的工厂 . )
例如,请参阅以下内容:
您可以看到编译器隐式选择返回类型以最佳匹配预期类型 . 根据您声明接收变量的方式,您会得到不同的结果 .
以下是指定构建器的等效方法 . 请注意,在这种情况下,编译器将根据构建器的类型推断出预期的类型:
Daniel Sobral的答案很棒,应该与Architecture of Scala Collections(Scala编程的第25章)一起阅读 .
我只是想详细说明为什么它被称为
breakOut
:为什么叫做breakOut?
因为我们想 break out of one type and into another :
突破什么类型成什么类型?让我们看一下
Seq
上的map
函数作为例子:如果我们想直接通过映射序列元素来构建Map,例如:
编译器会抱怨:
原因是Seq只知道如何构建另一个Seq(即有一个隐含的
CanBuildFrom[Seq[_], B, Seq[B]]
构建器工厂可用,但是从Seq到Map有 NO 构建器工厂) .为了编译,我们需要以某种方式 breakOut of the type requirement ,并能够构造一个构建器,为
map
函数生成一个Map来使用 .正如Daniel所解释的那样,breakOut具有以下特征:
Nothing
是所有类的子类,因此可以替换任何构建器工厂来代替implicit b: CanBuildFrom[Nothing, T, To]
. 如果我们使用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
的作用: