隐式转换Scala中的泛型和非泛型子类型

时间:2011-03-03 17:44:39

标签: generics scala implicit-conversion

假设您要为所有Iterables添加一些方法。这看起来像这样:

import collection.generic.CanBuildFrom

class Foo[P, S[X] <: Iterable[X]](val s : S[P]) {
  def bar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : S[P] = {
    val builder = bf(s)
    builder ++= s
    builder += j
    builder.result
  }

  def oneBar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : P = bar(j).head
}

implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)

现在,代码如

println(Seq(1,2,3,4) bar 5)

编译并顺利执行。但是,

println((1 to 4) bar 5)

原因

error: value bar is not a member of scala.collection.immutable.Range.Inclusive 
with scala.collection.immutable.Range.ByOne

我认为这可能是因为隐式转换要求(?)参数的类型具有类型参数(Range没有)。但

implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)

不会改变任何事情。请注意,Range扩展了Iterable[Int]

我做错了什么?如何编写一个适用于Iterable的所有子类型的隐式转换,是否通用?

编辑:我只是注意到更简单的

implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)

按预期工作(在REPL上)。或者是吗?这个解决方案有缺点吗?

编辑2:缺点是bar的静态结果类型只是Iterable[P],而不是更具体的类型。但是,构造的集合具有正确的(实际)类型。

1 个答案:

答案 0 :(得分:2)

可悲的是,Range延伸Iterable[Int]并不重要,这确实是类型参数类型的问题。它也是一个很深的版本,即使是核心库也会受到影响(只需查看Manifest中的注释)

如果想要使用地图,字符串等,您也会遇到它,就好像它们是Iterable一样。

我发现的唯一解决方案是为pimp类型定义多个隐式转换。

<强>更新

这里的问题是从提供的参数推断出类型参数P,该参数似乎没有类型参数。你实际上是想为类型构造函数做一些提取器将为常规构造函数做的事情,并且多态性正在阻碍。

您编辑的示例有效,因为不需要进行此特定推理,现在您只能返回Iterable,因此失去了CanBuildFrom的大部分好处

如果这不是问题,那么这是一个更简单的解决方案,所以请滚动它。

否则,你需要对你想要皮条客的每种可能的arity采用不同的含义。

更新2

在尝试确定Range是否为有效参数时,请考虑编译器如何处理不同的表达式:

拿1:

implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
  • S是一种类型较高的类型* => *
  • Range是一种类型*
  • 的简单类型
  • 种类不匹配,因此无效

拿2:

implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
  • 同样的问题,S仍然是* => *参数不匹配

拿3:

implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
  • 提供参数后,Iterable[P]是一种简单类型*
  • Range通过了第一个障碍
  • 第二项检查是Range是某些Iterable[P]的{​​{1}}的子类
  • P推断为P
  • 编译器很高兴所有推理,边界检查等都已成功