【问题标题】:Implicitly Convert Generic and Non-Generic Subtypes in Scala在 Scala 中隐式转换泛型和非泛型子类型
【发布时间】:2011-03-03 17:44:38
【问题描述】:

假设你想为所有的 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],而不是更具体的类型。但是,构造的集合具有正确的(实际)类型。

【问题讨论】:

    标签: generics scala implicit-conversion


    【解决方案1】:

    可悲的是,Range 扩展 Iterable[Int] 并不重要,这确实是类型参数的数量问题。这也是一个很深的问题,即使是核心库也有一些地方受到影响(看看Manifest 中的 cmets)

    如果您想像使用 Iterable 一样使用 Maps、Strings 等,也会遇到它。

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

    更新

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

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

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

    否则,您需要为每个可能的要拉皮条的类型的数量使用不同的隐式。

    更新 2

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

    采取 1:

    implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
    
    • S 是一种高级类型,类似于 * =&gt; *
    • Range 是一种简单类型,类似于 *
    • 种类不匹配所以无效

    采取 2:

    implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
    
    • 同样的问题,S 还是不错的 * =&gt; * 参数不匹配

    采取 3:

    implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
    
    • 已经提供了参数,Iterable[P] 是一种简单的类型*
    • Range 克服了这第一道障碍
    • 第二个检查是RangeIterable[P] 的子类,对于某些P
    • 是的,P 推断为Int
    • 编译器很高兴所有推断、边界检查等都成功

    【讨论】:

    • 它不应该适用于String(因为它只能以Seq 显示),但它应该适用于MapRange 和其他可怜的生物。如果是arity的问题,为什么第二个版本不起作用?如何定义第二个隐式转换来捕获 Iterable 的所有子类型而无需类型参数?
    • @update:我明白了。但是,我不清楚为什么推断者不考虑超类型。由于您不能使用不同的参数 (?) 扩展相同的特征,因此这应该会产生明确的结果。至于我更简单的版本,它创建了正确的(动态)集合类型,但只能静态输入Iterable。那就是您获得了 CanBuildFrom 的“幕后”优势,但没有打字优势。
    • 我已经扩展了我的答案以更详细地解释。
    • 感谢您的详细说明,但我明白了。我的观点是:在采取1(甚至2)时,为什么编译器在无法匹配后不走Range的超类型线性化,并重复你提到的步骤?当然,它会更多地增加编译器运行时间,但有可能,对吧?奇怪的是,Range 的每个实例都是 Iterable[Int],但此时没有被视为这样,就好像推断者忘记了这个事实一样。
    • @Raphael - 绝对有可能可以这样做,但目前不是这样,因此在测试 Kind 相等性时会发生故障。请放心,核心 Scala 开发团队已经知道这个问题,虽然我不知道什么时候会得到解决,但修复起来比看起来更难!
    猜你喜欢
    • 2013-11-15
    • 2011-09-12
    • 2018-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-25
    • 2017-09-10
    • 1970-01-01
    相关资源
    最近更新 更多