Scala 2.8 CanBuildFrom

28

继续我之前提出的另一个问题 Scala 2.8 breakout, 我想更了解一下Scala方法TraversableLike[A].map,其签名如下:

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

注意这个方法的一些细节:

  • 它接受一个将遍历器中的每个 A 转换为 B 的函数。
  • 它返回 That 并且需要一个类型为 CanBuildFrom[Repr, B, That] 的隐式参数。

我可以按照以下方式调用它:

> val s: Set[Int] = List("Paris", "London").map(_.length)
s: Set[Int] Set(5,6)

我不太理解的是编译器如何强制执行That绑定到B(也就是说,它是一些B的集合)这一事实。类型参数看起来与上面的签名以及CanBuildFrom特质本身的签名都是独立的:

trait CanBuildFrom[-From, -Elem, +To]
Scala编译器如何确保That不能被迫转换成不合理的东西?
Scala编译器如何确保That不能被强制转换成无意义的内容?
> val s: Set[String] = List("Paris", "London").map(_.length) //will not compile

编译器如何决定在调用时有哪些隐式的CanBuildFrom对象在范围内?


这里有一篇非常好的解释文章:http://blog.bruchez.name/2012/08/getting-to-know-canbuildfrom-without-phd.html - om-nom-nom
顺便说一下,这种用法有一个概念名称:返回类型多态性。 - lcn
2个回答

29

请注意,map的第二个参数是一个隐式参数。必须有一个具有适当类型的隐式参数存在于作用域中,否则您必须传递这样的参数。

在您的示例中,That 必须为 Set[String],B 必须为 IntRepr 必须为 List[String]。因此,为了编译通过,您需要在作用域中添加以下隐式对象:

implicit object X: CanBuildFrom[List[String], Int, Set[String]]

在作用域中不存在这样的东西。此外,breakOut 也无法提供它,因为它本身需要一个隐式的 CanBuildFrom,其第一个类型可以是任何类(一个协变的 Nothing 的后代),但受其他类型的限制。

例如,看一下 List 的伴生对象中的 CanBuildFrom 工厂:

implicit def  canBuildFrom [A] : CanBuildFrom[List, A, List[A]]  

由于它通过A绑定第二个和第三个参数,所以涉及到的隐式不起作用。

那么,如何确定在哪里寻找此类隐式?首先,Scala将一些内容导入所有范围。目前我能想到以下导入:

import scala.package._ // Package object
import scala.Predef._  // Object
// import scala.LowPriorityImplicits, class inherited by Predef
import scala.runtime._ // Package

由于我们关心的是隐式转换,需要注意的是当你从包中导入内容时,唯一可能出现的是单例隐式。但是当你从对象(单例)中导入内容时,可以有隐式定义、值和单例。

现在,在PredefLowPriorityImplicits中存在与字符串相关的CanBuildFrom隐式。它们使我们能够编写"this is a string" map (_.toInt)

那么,除了这些自动导入和显式导入之外,在哪里还可以找到隐式呢?一个地方:正在应用方法的实例的伴生对象。

我说伴生对象s,因为问题实例所继承的所有特征和类的伴生对象都可能包含相关隐式。我不确定实例本身是否可能包含一个隐式。老实说,我现在无法重现这个情况,所以我肯定犯了某种错误。

无论如何,看看伴生对象内部。


Daniel - 我怎么知道在我的代码的某个点上有哪些implicit对象在作用域内?我意识到这就是问题的关键。它在哪里被指定了?此外,这个问题与breakOut无关。 - oxbow_lakes
除了对象scala.Predef中的隐式转换之外,您必须在相同或封闭作用域中定义隐式,或者明确导入该隐式。 - Randall Schulz
当你从包中导入东西时,唯一可能的隐式是单例。但是在Scala 2.8包对象中是否仍然如此呢?它们也可以包含隐式定义和值。 - Jean-Philippe Pellet
@JPP包对象既不是包也不是对象,它们是包对象。对象规则适用于它们。 - Daniel C. Sobral
我的意思是,如果你写了 import scala._,你会导入所有在 scala 包中定义的类和特质,以及 scala 包对象中的所有内容,其中可能包含隐式值和函数(或者我错了吗?)。但当然,scala 包已经预先导入了,这只是一个例子。 - Jean-Philippe Pellet

0
object ArrayBuffer extends SeqFactory[ArrayBuffer] {
  /** $genericCanBuildFromInfo */
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, ArrayBuffer[A]] = new GenericCanBuildFrom[A]
  def newBuilder[A]: Builder[A, ArrayBuffer[A]] = new ArrayBuffer[A]
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接