【问题标题】:Why high order implicits are ignored in some cases?为什么在某些情况下会忽略高阶隐式?
【发布时间】:2016-06-10 09:48:08
【问题描述】:

我收到一个奇怪的编译器错误,该错误实际上存在但由于某种原因无法找到。所以我构建了一个重现神秘行为的小测试用例。

trait Hide {
  type T
}
object HideString extends Hide {
  override type T = String
}
object HideBool extends Hide {
  override type T = Boolean
}

简单类型用作隐式转换的明确目标。

def id[H <: Hide, C](x : C)(implicit ev : C => H#T) : H#T = ev(x)
def drop[H <: Hide, C](x : C)(implicit ev : C => H#T) : Int = {
  println(ev(x))
  1
}
def idSeq[H <: Hide, C](x : Seq[C])(implicit ev : Seq[C] => Seq[H#T]) : Seq[H#T] = ev(x)
def dropSeq[H <: Hide, C](x : Seq[C])(implicit ev : Seq[C] => Seq[H#T]) : Int = {
  println(ev(x))
  1
}

依赖隐式转换的方法。它基本上是 2x2 矩阵。 id 方法返回转换后的类型,drop 方法在内部使用转换并返回一些常量。普通方法对精确隐式转换的类型进行操作,Seq 方法对序列进行操作。

implicit def exString(x : String) : HideString.type#T = x
implicit def highString[F[_]](x : F[String]) : F[HideString.type#T] = x

以上隐式转换highString 是用高阶类型定义的。

val s1 = id("sdf")
val s2 = drop("aero")

val r1 = idSeq(Seq("a", "bc"))
val r2 = dropSeq(Seq("i", "IO"))

尝试实际使用转换会给我带来一个错误:

ImplicitResolution.scala:98: error: No implicit view available from Seq[String] => Seq[test.implicits.HighReduction.Hide#T].
  val r2 = dropSeq(Seq("i", "IO"))

这可以概括为以下矩阵:

|        | id   | drop |
|--------+------+------|
| normal | pass | pass |
| seq    | pass | fail |

如果我对dropSeq 方法使用精确定义的隐式转换,则可以正常找到:

implicit def seqBool(x : Seq[Boolean]) : Seq[HideBool.type#T] = x

val a1 = idSeq(Seq(true, false))
val a2 = dropSeq(Seq(false, true))

此外,如果我明确指定隐式参数 dropSeq 开始工作:

val r2i = dropSeq(Seq("i", "IO"))(highString[Seq] _)

这是最奇怪的事情。 highString 隐式符合所有要求。并且它被声明为implicit,所以它应该被编译器找到。在idSeq 的情况下,它实际上是被发现的。那么,为什么在dropSeq 的情况下会忽略它呢?

【问题讨论】:

    标签: scala implicit


    【解决方案1】:

    在您的情况下,idSeqdropSeq 之间的唯一区别是返回类型:您在 Scala 编译器中遇到了一些极端情况,值得向 Scala 社区发出信号。

    也就是说,您的 idSeq 签名错误:H#X 并不表示指定 H 类型的 X 类型,而是表示任何 H 实例的 X(不是已由编译器,请参阅此处的 Daniel Sobral 解释 What does the `#` operator mean in Scala?)

    您可能想要做的是在 H 和您的结果类型之间建立关系,如果您引入类型别名以获得更易读的签名,这会更容易:

    object Hide {
      type HideAux[X] = Hide { type T = X}
    }
    

    然后你可以像这样重写你的代码:

      def idSeq[B,H <: HideAux[B], C](x : Seq[C])(implicit ev : Seq[C] => Seq[B]) : Seq[B] = ev(x)
      def dropSeq[B,H <: HideAux[B], C](x : Seq[C])(implicit ev : Seq[C] => Seq[B]) : Int = {
        println(ev(x))
        1
      }
    

    这段代码可以编译,并且还要注意,如果您正确使用泛型和类型类,您将不需要ididSeq 这两个不同的方法,因为动态行为将由类型类本身提供。

    【讨论】:

    • 如果您针对您描述的“Scala 编译器中的极端情况”提出问题,我会更倾向于奖励您。我认为这是一些极端情况,但您仍然没有提供足够的信息让我知道这是 expected 行为,还是真的是编译器中的错误。如果你能摆脱那个悬空的}...
    • 您的两个函数除了返回类型外具有相同的签名,但是您对 r1 和 r2 使用类型推断(因此返回类型不能用于隐式解析)。如果可以使用可用的类型信息解决 r1 的隐式,则可以解决 r2 的隐式:假设 drop 有效而 dropSeq 无效,则问题可能出在泛型类型和类型投影上。
    • 使用-Xlog-implicits 会产生一些有趣的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多