为什么在某些情况下会忽略高阶隐含?

ayv*_*ngo 8 scala implicit

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

trait Hide {
  type T
}
object HideString extends Hide {
  override type T = String
}
object HideBool extends Hide {
  override type T = Boolean
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

依赖隐式转换的方法.它基本上是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
Run Code Online (Sandbox Code Playgroud)

上面的隐式转换highString是使用高阶类型定义的.

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

val r1 = idSeq(Seq("a", "bc"))
val r2 = dropSeq(Seq("i", "IO"))
Run Code Online (Sandbox Code Playgroud)

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

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

这可以归纳为以下矩阵:

|        | id   | drop |
|--------+------+------|
| normal | pass | pass |
| seq    | pass | fail |
Run Code Online (Sandbox Code Playgroud)

如果我使用精确定义的隐式转换dropSeq方法,通常会找到:

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

val a1 = idSeq(Seq(true, false))
val a2 = dropSeq(Seq(false, true))
Run Code Online (Sandbox Code Playgroud)

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

val r2i = dropSeq(Seq("i", "IO"))(highString[Seq] _)
Run Code Online (Sandbox Code Playgroud)

这是最奇怪的事情.highString隐含适合所有要求.它被声明为implicit,所以它应该由编译器找到.如果idSeq它实际上被发现了.那么,为什么在这种dropSeq情况下被忽略呢?

Edm*_*984 1

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

也就是说,你的 idSeq 有错误的签名:H#X并不意味着指定类型的 X 类型H,而是XH 的任何实例(不是编译器已解析的实例,请参阅 Daniel Sobral 的解释此处` # 是什么意思) ` Scala 中的运算符意味着什么?

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

object Hide {
  type HideAux[X] = Hide { type T = X}
}
Run Code Online (Sandbox Code Playgroud)

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

  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
  }
Run Code Online (Sandbox Code Playgroud)

此代码编译后,还要注意,如果正确使用泛型和类型类,则不需要两种不同的方法ididSeq因为动态行为将由类型类本身提供。

  • 如果您针对您所描述的“Scala 编译器中的极端情况”提出问题,我会更倾向于奖励您。我认为这是一些极端情况,但您仍然没有提供足够的信息让我知道这是否是*预期的*行为,或者是否确实是编译器中的错误。如果你能去掉那个悬空的`}`,那就太好了...... (2认同)