我正在尝试一组自定义容器函数,并从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
。这个假设得到以下事实的支持:它足以提供目标类型(没有额外的显式导入!!)来获得隐式解析。
答案 0 :(得分:3)
extra type参数根据隐式解析的规范启用此行为。以下是where do implicits come from的常见问题解答摘要(相关部分加粗):
首先看当前范围:
- 当前范围中定义的隐含
- 明确导入
- 通配符导入
现在查看相关类型:
- 类型(1)的伴随对象
- 参数类型的隐含范围(2)
- 类型参数的隐含范围(3)
- 嵌套类型的外部对象
- 其他尺寸
当您在map
上调用Mappable
时,您会从其参数的隐式范围(2)中获得隐含。由于在这种情况下它的参数是Factory
,这意味着你也可以从Factory
的所有类型参数的隐式范围中得到隐含式(3)。接下来是关键:只有当您将Source
作为类型参数添加到Factory
时,才能获得Container
隐式范围内的所有隐含。由于Container
的隐式范围包括其伴随对象中定义的含义(通过(1)),因此在没有您使用的导入的情况下,将所需的隐式带入范围。
这是语法上的原因,但是这是一个很好的语义原因,这是理想的行为 - 除非指定了类型,否则当可能存在冲突时,编译器无法解析正确的隐含。例如,如果在String
或List[Char]
的构建器之间进行选择,则编译器需要选择正确的构建器以使"myFoo".map(_.toUpperCase)
返回String
。如果每次隐式转换都被纳入范围,那么这将是困难或不可能的。上述规则旨在包含可能与当前范围相关的有序列表,以防止出现此问题。
您可以在the specification或this great answer中详细了解隐含和隐含范围。
以下是其他两个解决方案的工作原理:当您明确指定对map
的调用的返回类型时(在没有Source
参数的版本中),则输入类型推断在游戏中,编译器可以推断出感兴趣的参数That
是Container
,因此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
我们将在Factory
,Repr
,B
和That
的伴随对象中定义隐含。正如Ben Reich在他的回答中指出的那样,这解释了为什么Repr
类型参数是查找隐式参数所必需的。
这就是它使用显式定义的返回类型
的原因val mapped: Container[Int] = c.map { x => 2 * x }
使用显式返回类型定义,类型推断会为Container[Int]
中的That
参数选择Factory[Repr, B, That]
。因此,Container
中定义的含义也可用。