【问题标题】:Why do we need the From type parameter in Scala's CanBuildFrom为什么我们需要 Scala 的 CanBuildFrom 中的 From 类型参数
【发布时间】:2015-05-22 08:45:43
【问题描述】:

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

在 Scala 的集合中,CanBuildFrom 支持对函数的返回类型进行参数化,例如 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._ 或显式指定预期返回类型 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))
}

并更改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 的所有隐式都可用作隐式参数,包括我的@ 987654352@。这一假设得到了以下事实的支持:提供目标类型(无需额外的显式导入!!)即可获得隐式解析。

【问题讨论】:

  • 我目前无法写出完整的答案,但From 的一个重要作用是指导隐式选择。我们希望"foo".map(_.toUpper)List('f', 'o', 'o').map(_.toUpper) 有不同的返回类型,所以它们需要接收不同的实例。
  • 它在隐含参数类型的伴生对象中寻找隐含,而不是源类型。

标签: scala implicit


【解决方案1】:

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

首先查看当前范围:

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

现在查看以下关联类型:

  • 类型 (1) 的伴随对象
  • 参数类型的隐式范围 (2)
  • 类型参数的隐式范围 (3)
  • 嵌套类型的外部对象
  • 其他尺寸

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

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

您可以在the specificationthis great answer 中阅读有关隐式和隐式范围的更多信息。

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

至于您的元问题,您可以随时点击源代码(或查看存储库),构建最小示例(就像您拥有的那样),然后在这里提问。使用 REPL 有助于处理这样的小示例。

【讨论】:

    【解决方案2】:

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

    首先,符合条件的所有标识符 x 都可以在 没有前缀的方法调用点,表示隐式 定义或隐式参数。因此,合格的标识符可以 是本地名称,或封闭模板的成员,或者它可能是 已通过导入子句无需前缀即可访问。

    因此,每个没有限定条件的可用名称以及用implicit 关键字标记的名称都可以用作隐式参数。

    第二个符合条件的也是某个对象的所有隐式成员 属于隐式参数类型 T 的隐式范围。 类型 T 的隐式作用域由 与隐式参数类型相关联的类。这里, 我们说一个类 C 与一个类型 T 相关联,如果它是一个基类 T的一部分。

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

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

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

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

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

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

    【讨论】:

    • 看起来审稿人将其误解为只是对问题的澄清而非答案,因为您的写作风格和修辞问题的语气存在不确定性。
    猜你喜欢
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    • 2015-10-23
    • 1970-01-01
    相关资源
    最近更新 更多