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

时间:2015-05-22 08:45:43

标签: scala implicit

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

在Scala的集合中,CanBuildFrom启用了map等函数的返回类型的参数化。在内部,CanBuildFrom参数与工厂一样使用:map在其输入元素上应用它的第一顺序函数,将每个应用程序的结果输入CanBuildFrom参数,最后调用CanBuildFrom的result方法,该方法构建map调用的最终结果。

据我了解,-From的{​​{1}}类型参数仅用于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)) }

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
}

会产生错误消息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类型参数的情况下应该有效可能会有所帮助。

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

2 个答案:

答案 0 :(得分:3)

extra type参数根据隐式解析的规范启用此行为。以下是where do implicits come from的常见问题解答摘要(相关部分加粗):

  

首先看当前范围:

     
      
  • 当前范围中定义的隐含
  •   
  • 明确导入
  •   
  • 通配符导入
  •   
     

现在查看相关类型:

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

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

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

您可以在the specificationthis great answer中详细了解隐含和隐含范围。

以下是其他两个解决方案的工作原理:当您明确指定对map的调用的返回类型时(在没有Source参数的版本中),则输入类型推断在游戏中,编译器可以推断出感兴趣的参数ThatContainer,因此Container的隐含范围进入范围,包括其伴随对象隐含。当您使用显式导入时,所需的隐含在范围内,非常简单。

至于你的meta-question,你总是可以点击源代码(或者只是检查repo),构建最小的例子(就像你一样),并在这里提问。使用REPL可以帮助处理这样的小例子。

答案 1 :(得分:0)

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

  

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

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

  

第二个符合条件的也是某些对象的隐式成员   属于隐式参数类型的隐式范围,T。      类型T的隐式范围包括所有的伴随模块   与隐式参数的类型相关联的类。这里,   我们说如果C类是基类,则类C与类型T相关联   T的某些部分。

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

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 }

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