为什么我们需要Scala的CanBuildFrom中的From类型参数
我正在试验一组自定义的容器函数,并从Scala的集合库中获取了关于CanBuildFrom[-From, -Elem, -To]
隐式参数的CanBuildFrom[-From, -Elem, -To]
。
在Scala的集合中, CanBuildFrom
支持参数化返回类型的函数,如map
。 在内部, CanBuildFrom
参数像工厂一样使用: map
在其输入元素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))
}
和一个实现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
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 }
时,错误消失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
类型参数的情况下工作可能会有所帮助。
根据这个关于隐式解析的文档条目,在类型的伴随对象中查找含义。 作者没有提供来自伴随对象的隐式参数的示例(他只解释了隐式转换),但我认为这意味着对Container[A]#map
应该使object Container
所有暗示都可用作隐式参数,包括我的elementFactory
。 这个假设得到了以下事实的支持:它足以提供目标类型(无额外的显式导入!!)以获得隐式解析。
额外的类型参数根据隐式分辨率的规格启用此行为。 以下是常见问题答案中的含义以及含义来源(相关部分粗体):
首先看当前的范围:
现在看看相关的类型:
当你在Mappable
上调用map
时,你会从(2)中得到它的参数隐含范围的Mappable
。 由于在这种情况下它的参数是Factory
,这意味着您也可以通过(3)从Factory
的所有类型参数的隐式范围中获得含义。 接下来是关键:只有当您将Source
作为类型参数添加到Factory
才会在Container
的隐式作用域中获得所有Container
。 由于Container
的隐式作用域包括在其伴随对象中定义的含义(由(1)),因此这会将所需的隐式转换为没有您使用的导入的范围。
这是语法原因,但是这是一个很好的语义理由,因为它是所需的行为 - 除非指定了类型,否则当可能存在冲突时,编译器无法解决正确的隐式。 例如,如果在String
或List[Char]
构建器之间进行选择,编译器需要选择正确的一个以启用返回String
"myFoo".map(_.toUpperCase)
。 如果每个隐含的转换都一直进入范围,这将是困难的或不可能的。 上述规则旨在包含有关可能与当前范围相关的有序列表,以防止出现此问题。
你可以在规范或这个很好的答案中阅读更多关于蕴含和隐含范围的内容。
这就是为什么你的其他两个解决方案工作的原因:当你明确指定要map
的调用的返回类型(在没有Source
参数的版本中),那么类型推断就会起作用,编译器可以推断出感兴趣的参数That
Container
,并且因此隐含的范围Container
进入活动范围,包括其同伴对象implicits。 当你使用一个明确的导入时,那么所需的隐含在范围内是平凡的。
至于你的元问题,你可以随时点击源代码(或只是检出回购),构建最少的示例(就像你有),并在这里提问。 使用REPL有助于处理这样的小例子。
该规范有关隐式参数分辨率的专用部分。 根据该部分,隐含参数的参数有两个来源。
首先,符合条件的所有标识符x都可以在方法调用时访问,而不需要前缀,并且表示隐式定义或隐式参数。 合格的标识符因此可以是本地名称或封闭模板的成员,或者可以通过导入子句使其无需前缀而被访问。
因此,每一个没有资格可用的名称和用implicit
关键字标记的名称都可以用作隐式参数。
第二个符合条件的对象也都是属于隐式参数类型T的隐式范围的所有对象的隐式成员。类型T的隐式范围由与隐式参数的类型关联的所有类的伴随模块组成。 在这里,我们说如果一个类C是T的某个部分的基类,那么它与一个类型T相关联。
在隐式参数列表类型的所有部分的伴随对象(又名伴随模块)中定义的隐含也可用作参数。 所以在最初的例子中
def map[B, That](f: A => B)(implicit factory: Factory[Repr, B, That]): That
我们将得到Factory
, Repr
, B
和That
的伴随对象中定义的Repr
。 正如Ben Reich在他的回答中指出的那样,这解释了为什么Repr
类型参数对于找到隐式参数是必要的。
这就是为什么它使用显式定义的返回类型
val mapped: Container[Int] = c.map { x => 2 * x }
使用显式返回类型定义,类型推断为Factory[Repr, B, That]
的That
参数选择Container[Int]
Factory[Repr, B, That]
。 因此, Container
中定义的Container
也可用。
上一篇: Why do we need the From type parameter in Scala's CanBuildFrom
下一篇: 1) + T(n/2) + n