使用逆变泛型类型时的隐含值不明确

ambiguous implicit values when using contravariant generic type

我在 Scala 宏中遇到了 inferImplicitValue 问题。我在玩 play 的 json 库 Format[T] 的宏。 我可以将其缩小到一个问题,即 Writes[T] 有时是如何用 OWrites[T] 实现的。连同显式类型声明 implicit val,这导致了以下编译器错误。

[error] ambiguous implicit values:
[error]  both value xm in object TestTest of type => OMaterializer[X]
[error]  and value tm in object TestTest of type => Materializer[T]
[error]  match expected type Materializer[X]
[error] one error found
[error] (root/compile:compile) Compilation failed

我们看代码(SBT项目可以在这里找到,https://github.com/q42jaap/scala-macro-inferImplicitValue

// models play json lib's Writes
trait Materializer[-T]

// models play json lib's OWrites
trait OMaterializer[T] extends Materializer[T]

trait T
case class X() extends T

object TestTest {
  // The OMaterializer trait here is the second part of the problem
  implicit val xm = new OMaterializer[X] {}
  // the explicit `tm: Materializer[T]` type declaration here is first part of the problem
  implicit val tm: Materializer[T] = Macro.genMaterializer[T, X]
}

object Macro {
  def genMaterializer[T, M]: Materializer[T] = macro MacroImpl.genMaterializer[T, M]
}

object MacroImpl {
  def genMaterializer[T: c.WeakTypeTag, M: c.WeakTypeTag](c: blackbox.Context): c.Expr[Materializer[T]] = {
    val tMaterializerTpe = c.universe.appliedType(c.typeOf[Materializer[_]], c.weakTypeOf[M])
    c.inferImplicitValue(tMaterializerTpe)
    c.universe.reify {
      new Materializer[T] {}
    }
  }
}

请注意 tm 的显式类型声明,将其删除即可解决问题。当 xmMaterializer[X] 而不是 OMaterializer[X] 时,它也有效。

inferImplicitValue 在查找 Materializer[X] 时同时考虑 tmxm。当 xm 的类型为 Materializer[X] 并且 tm 的类型为 Materializer[T] 推断者更喜欢 xm 而不是 tm,因为它是完全匹配。但是当 xmOMaterializer[X] 时,编译器无法再决定是哪一个 更好并抛出错误。

正如我所说,从 tm 中删除显式类型声明可以解决问题,因为在执行宏时只有 xm 的类型是已知的。

我可以解决这个问题inferImplicitValue了? silent 选项已经为真(默认情况下)。

在我的实际用例中,我有多个 T 的实现(XYZ),然后使用联合类型(如 reactivemongo做)到宏:

genMaterializer[T, Union[X \/ Y \/ Z]]

所以我必须使用 inferImplicitValue 来找到 X、Y 和 Z 的 Materializer。

请注意,我已将此案例进一步简化为

object ThisDoesntWorkToo {
  implicit val xm = new OMaterializer[X] {}

  implicit val tm: Materializer[T] = withoutMacro[X]

  def withoutMacro[A](implicit m: Materializer[A]): Materializer[A] = m
}

它不使用宏,但有相同的编译器错误:

[error] TestTest.scala:15: ambiguous implicit values:
[error]  both value xm in object ThisWorks of type => OMaterializer[X]
[error]  and value tm in object ThisWorks of type => Materializer[T]
[error]  match expected type Materializer[X]
[error]   implicit val tm: Materializer[T] = withoutMacro[X]

这简化了这里的情况,但仍然给我留下了隐式 val 的实现可以引用自身的问题。 在后一种情况下,简单明了的方法是显式提供隐式值,但正如在宏的最终版本中所论证的那样,我需要使用 inferImplicitValue 因为我有一个类型列表,我必须为其找到一个 Materializer。

我不确定你所说的 "fixes" 或 "it works" 是什么意思,但这只是工作中的重载解析。

当两者都是Materializer时,tm获胜,因为Mat[T] <:< Mat[X]

如果用例是在该范围内引入隐式 tm,但隐式地选择 xm,那么这是我能想出的唯一技巧:

  implicit val tm: Materializer[T] = {
    val tm = 0
    Macro.genMaterializer[T, X]
  }

通过简单地从显式范围中消除隐式 tm 来工作。

也许一个丑陋的宏可以自动生成那个块,从中展开真正的宏。这打破了局部性。

通常,您通过使隐含不明确来消除隐含,但您希望隐含在此范围内。因此,阴影仅将其从嵌套范围中删除。

这没有帮助,但很自然:

object X {
  implicit val xm: OMaterializer[X] = new OMaterializer[X] {}
}