为什么我们需要Scala的CanBuildFrom中的From type参数

Joh*_*ong 7 scala implicit

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

在Scala的集合中,CanBuildFrom支持返回类型函数的参数化map.在内部,CanBuildFrom参数像工厂一样使用:map在它的输入元素上应用它的第一个顺序函数,将每个应用程序的结果提供给CanBuildFrom参数,最后调用CanBuildFrom的result方法来构建调用的最终结果map.

在我的理解中,-From类型参数CanBuildFrom[-From, -Elem, -To]仅用于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))
}
Run Code Online (Sandbox Code Playgroud)

和实施 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
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,打电话给地图

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

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

当我添加一个未使用的第三类参数时,所有这些"变通办法"都变得不必要了 Factory

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))
}
Run Code Online (Sandbox Code Playgroud)

并更改隐式定义 Container

object Container {
  implicit def elementFactory[A, B] = new Factory[Container[A], B, Container[B]] {
    def apply(elems: Traversable[A]) = new Container(elems.toSeq)
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

澄清

解释为什么我认为隐式解决方案应该在没有附加-Source类型参数的情况下工作可能会有所帮助.

根据关于隐式解析的文档条目,在类型的伴随对象中查找含义.作者没有提供来自伴随对象的隐式参数的示例(他只解释了隐式转换),但我认为这意味着调用Container[A]#map应该使所有隐含object Container可用作隐式参数,包括my elementFactory.这个假设得到以下事实的支持:它足以提供目标类型(没有额外的显式导入!!)来获得隐式解析.

Ben*_*ich 3

额外的类型参数根据隐式解析的规范启用此行为。以下是关于隐式从何而来的常见问题解答的摘要(相关部分以粗体显示):

\n
\n

首先查看当前范围:

\n
    \n
  • 当前范围内定义的隐式
  • \n
  • 显式导入
  • \n
  • 通配符导入
  • \n
\n

现在查看相关类型:

\n
    \n
  • 类型的伴生对象 (1)
  • \n
  • 参数\xe2\x80\x99s 类型的隐式范围 (2)
  • \n
  • 类型参数的隐式作用域 (3)
  • \n
  • 嵌套类型的外部对象
  • \n
  • 其他尺寸
  • \n
\n
\n

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

\n

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

\n

您可以在规范这个很棒的答案中阅读有关隐式和隐式范围的更多信息。

\n

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

\n

至于您的元问题,您始终可以单击源代码(或仅查看存储库),构建最小的示例(就像您拥有的那样),然后在此处提出问题。使用 REPL 对处理这样的小例子有很大帮助。

\n