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

Generics 在Scala中隐式转换泛型和非泛型子类型,generics,scala,implicit-conversion,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

假设您想向所有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((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
没有)。但是

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


编辑2:缺点是
条形图的静态结果类型将仅为
Iterable[p]
,而不是更具体的类型。不过,构造的集合具有正确的(实际)类型。

遗憾的是,
Range
扩展
Iterable[Int]
并不重要,这确实是类型参数的算术问题。这也是一个深层次的问题,即使核心库在某些地方也会受到影响(只需查看
清单中的注释即可)

如果您想使用映射、字符串等,就好像它们是
Iterable
一样,您也会遇到它

我找到的唯一解决方案是定义多个到pimp类型的隐式转换

更新

这里的问题是从提供的参数推断类型参数
p
,该参数似乎没有类型参数。实际上,您正在尝试对类型构造函数执行提取器对常规构造函数执行的操作,而多态性正在成为阻碍

您编辑的示例之所以有效,是因为不需要这个特定的推断,问题是您现在只能返回一个
Iterable
,因此失去了
CanBuildFrom

如果这不是一个问题,那么这是一个更简单的解决方案,所以继续吧

否则,对于要拉皮条的类型的每个可能的arity,您将需要不同的隐式

更新2

当试图确定
范围
是否为有效参数时,请考虑编译器可能如何处理不同的表达式:

以1为例:

implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
  • 同样的问题,
    S
    仍然是参数不匹配的类型
第三个例子:

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

它不适用于
字符串
(因为它只能作为
序列
查看),但它应该适用于
地图
范围
和其他可怜的生物。如果这是一个算术问题,为什么第二个版本不起作用?我如何定义第二个隐式转换来捕获没有类型参数的
Iterable
的所有子类型?@update:我明白了。然而,我不清楚为什么推理者不考虑超类型。由于不能用不同的参数(?)扩展同一特性,因此应该会产生明确的结果。至于我的简单版本,它创建了正确的(动态)集合类型,但只能静态地键入
Iterable
。也就是说,您获得了
CanBuildFrom
的“幕后”优势,但没有打字优势。我已经扩展了我的答案,以更详细地解释。感谢您的详细阐述,但我也理解了这一点。我的观点是:在第1步(甚至第2步)中,为什么编译器在无法匹配后不进行
Range
的超类型线性化,并重复您提到的步骤?当然,这会增加编译器的运行时间,但这是可能的,对吗?奇怪的是,
Range
的每个实例都是一个
Iterable[Int]
,但在这一点上并没有这样对待,好像推理者忘记了这个事实。@Raphael-完全有可能是这样做的,但目前不是这样,因此在测试种类相等性时会出现失败。请放心,核心Scala开发团队已经知道了这个问题,尽管我不知道什么时候会得到解决,但修复起来比看起来更难!
implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)