Scala的CanBuildFrom为什么需要From类型参数

Why do we need the From type parameter in Scala's CanBuildFrom

我正在试验一组自定义容器函数,并从 Scala 的集合库中获得关于 CanBuildFrom[-From, -Elem, -To] 隐式参数的灵感。

在 Scala 的集合中,CanBuildFrom 启用 return 类型函数的参数化,例如 map。在内部,CanBuildFrom 参数像工厂一样使用:map 将它的一阶函数应用于它的输入元素,将每个应用程序的结果提供给 CanBuildFrom 参数,最后调用 CanBuildFrom 的 result 方法构建 map 调用的最终结果。

根据我的理解,CanBuildFrom[-From, -Elem, -To]-From 类型参数仅用于 apply(from: From): Builder[Elem, To],它创建了一个用某些元素预初始化的 Builder。在我的自定义容器函数中,我没有那个用例,所以我创建了自己的 CanBuildFrom 变体 Factory[-Elem, +Target].

现在我可以拥有特质了Mappable

trait Factory[-Elem, +Target] {
  def apply(elems: Traversable[Elem]): Target
  def apply(elem: Elem): Target = apply(Traversable(elem))
}

trait Mappable[A, Repr] {
  def traversable: Traversable[A]

  def map[B, That](f: A => B)(implicit factory: Factory[B, That]): That =
    factory(traversable.map(f))
}

和实现 Container

object Container {
  implicit def elementFactory[A] = new Factory[A, Container[A]] {
    def apply(elems: Traversable[A]) = new Container(elems.toSeq)
  }
}

class Container[A](val elements: Seq[A]) extends Mappable[A, Container[A]] {
  def traversable = elements
}

不幸的是,调用 map

object TestApp extends App {
  val c = new Container(Seq(1, 2, 3, 4, 5))
  val mapped = c.map { x => 2 * x }
}

产生一条错误消息 not enough arguments for method map: (implicit factory: tests.Factory[Int,That])That. Unspecified value parameter factory 。当我添加显式导入 import Container._ 或当我明确指定预期的 return 类型时错误消失 val mapped: Container[Int] = c.map { x => 2 * x }

当我将未使用的第三类参数添加到 Factory

时,所有这些 "workarounds" 都变得不必要了
trait Factory[-Source, -Elem, +Target] {
  def apply(elems: Traversable[Elem]): Target
  def apply(elem: Elem): Target = apply(Traversable(elem))
}

trait Mappable[A, Repr] {
  def traversable: Traversable[A]

  def map[B, That](f: A => B)(implicit factory: Factory[Repr, B, That]): That =
    factory(traversable.map(f))
}

并更改 Container

中的隐式定义
object Container {
  implicit def elementFactory[A, B] = new Factory[Container[A], B, Container[B]] {
    def apply(elems: Traversable[A]) = new Container(elems.toSeq)
  }
}

最后我的问题是:为什么看似未使用的 -Source 类型参数是解析隐式所必需的?

作为一个额外的元问题:如果您没有有效的实现(集合库)作为模板,您如何解决这些类型的问题?

澄清

这可能有助于解释为什么我认为隐式解析应该在没有附加 -Source 类型参数的情况下工作。

根据 this doc entry on implicit resolution,在类型的伴随对象中查找隐式。作者没有提供来自伴随对象的隐式参数的示例(他只解释了隐式转换)但我认为这意味着调用 Container[A]#map 应该使来自 object Container 的所有隐式都可用作隐式参数,包括我的 elementFactory。这一假设得到以下事实的支持:提供目标类型(无需额外的显式导入!!)就足以获得隐式解析。

额外类型参数根据隐式解析规范启用此行为。以下是对 where do implicits come from 的常见问题解答的摘要(相关部分以粗体显示):

First look in current scope:

  • Implicits defined in current scope
  • Explicit imports
  • wildcard imports

Now look at associated types in:

  • Companion objects of a type (1)
  • Implicit scope of an argument’s type (2)
  • Implicit scope of type arguments (3)
  • Outer objects for nested types
  • Other dimensions

当您在 Mappable 上调用 map 时,您可以通过 (2) 从其参数的隐式范围中获取隐式。由于在这种情况下它的参数是 Factory,这意味着您还可以通过 (3) 从 Factory 的所有类型参数的隐式范围中获取隐式。接下来是关键:只有当您将 Source 作为类型参数添加到 Factory 时,您才能获得 Container 隐式范围内的所有隐式。由于 Container 的隐式范围包括在其伴随对象中定义的隐式(通过 (1)),这会将所需的隐式带入范围,而无需您使用的导入。

这是句法上的原因,但有一个很好的语义原因使它成为所需的行为——除非指定类型,否则编译器将无法在可能存在冲突时解析正确的隐式。例如,如果在 StringList[Char] 的构建器之间进行选择,编译器需要选择正确的构建器以启用 "myFoo".map(_.toUpperCase) returning String。如果始终将每个隐式转换都纳入范围,这将很困难或不可能。上述规则旨在包含可能与当前范围相关的内容的有序列表,以防止出现此问题。

您可以在 the specification or in this great answer.

中阅读有关隐式和隐式范围的更多信息

这就是您的其他两个解决方案有效的原因:当您明确指定 map 调用的 return 类型时(在没有 Source 参数的版本中),然后键入推理开始发挥作用,编译器可以推断出感兴趣的参数 ThatContainer,因此 Container 的隐式范围进入范围,包括它的伴随对象隐式。当您使用显式导入时,所需的隐式在范围内,很简单。

至于你的元问题,你总是可以点击源代码(或者只是检查回购协议),建立最小的例子(就像你有的),然后在这里提问。使用 REPL 对处理像这样的小例子有很大帮助。

规范有专门的 section on implicit parameter resolution。根据该部分,隐式参数的参数有两个来源。

First, eligible are all identifiers x that can be accessed at the point of the method call without a prefix and that denote an implicit definition or an implicit parameter. An eligible identifier may thus be a local name, or a member of an enclosing template, or it may be have been made accessible without a prefix through an import clause.

因此,所有无限定可用且标有 implicit 关键字的名称都可以用作隐式参数。

second eligible are also all implicit members of some object that belongs to the implicit scope of the implicit parameter's type, T.   The implicit scope of a type T consists of all companion modules of classes that are associated with the implicit parameter's type. Here, we say a class C is associated with a type T if it is a base class of some part of T.

在隐式参数列表类型的所有部分的伴随对象(a.k.a 伴随模块)中定义的隐式也可用作参数。所以在原来的例子中

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

我们将在 FactoryReprBThat 的伴生对象中获得隐式定义。正如 Ben Reich 在他的回答中指出的那样,这解释了为什么需要 Repr 类型参数才能找到隐式参数。

这就是它与显式定义的 return 类型一起工作的原因

val mapped: Container[Int] = c.map { x => 2 * x }

使用显式 return 类型定义,类型推断为 Factory[Repr, B, That] 中的 That 参数选择 Container[Int]。因此,Container 中定义的隐式也可用。