类型成员的上下文边界或如何将隐式解析推迟到成员实例化

Context bounds for type members or how to defer implicit resolution until member instantiation

在下面的示例中,是否有办法避免隐式解析选择 defaultInstance 并改用 intInstance?代码后的更多背景:

// the following part is an external fixed API

trait TypeCls[A] {
  def foo: String
}

object TypeCls {
  def foo[A](implicit x: TypeCls[A]) = x.foo

  implicit def defaultInstance[A]: TypeCls[A] = new TypeCls[A] {
    def foo = "default"
  }

  implicit val intInstance: TypeCls[Int] = new TypeCls[Int] {
    def foo = "integer"
  }
}

trait FooM {
  type A
  def foo: String = implicitly[TypeCls[A]].foo
}

// end of external fixed API

class FooP[A:TypeCls] { // with type params, we can use context bound
  def foo: String = implicitly[TypeCls[A]].foo
}

class MyFooP extends FooP[Int]

class MyFooM extends FooM { type A = Int }

object Main extends App {

  println(s"With type parameter: ${(new MyFooP).foo}")
  println(s"With type member:    ${(new MyFooM).foo}")
}

实际输出:

With type parameter: integer
With type member:    default

期望的输出:

With type parameter: integer
With type member:    integer

我正在与第三方库合作,该库使用上述方案为类型 class TypeCls 提供 "default" 个实例。我认为上面的代码是演示我的问题的最小示例。

用户应该混合 FooM 特征并实例化抽象类型成员 A。问题是由于 defaultInstance (new MyFooM).foo 的调用没有解析专门的 intInstance 而是提交给 defaultInstance 这不是我想要的。

我添加了一个使用类型参数的替代版本,称为 FooP(P = 参数,M = 成员),它避免通过使用类型参数上的上下文绑定来解析 defaultInstance

是否有对类型成员执行此操作的等效方法?

编辑:我的简化有一个错误,实际上 foo 不是 def 而是 val,所以不可能添加隐式参数。所以目前的答案都不适用。

trait FooM {
  type A
  val foo: String = implicitly[TypeCls[A]].foo
}

// end of external fixed API

class FooP[A:TypeCls] { // with type params, we can use context bound
  val foo: String = implicitly[TypeCls[A]].foo
}

在这种特定情况下,最简单的解决方案是 foo 本身需要 TypeCls[A] 的隐式实例。 唯一的缺点是它会在每次调用 foo 时传递,而不是仅在实例化时传递 FooM。因此,您必须确保它们在每次调用 foo 时都在范围内。虽然只要 TypeCls 实例在伴随对象中,您就没有什么特别要做的。

trait FooM {
  type A
  def foo(implicit e: TypeCls[A]): String = e.foo
}

UPDATE:在我上面的回答中,我设法忽略了 FooM 无法修改的事实。此外,对问题的最新编辑提到 FooM.foo 实际上是 val 而不是 def.

好吧,坏消息是您使用的 API 完全坏了。 FooM.foo 不可能 return 任何有用的东西(它总是将 TypeCls[A] 解析为 TypeCls.defaultInstance 而不管 A 的实际值)。唯一的出路是在 A 的实际值已知的派生 class 中覆盖 foo,以便能够使用 TypeCls 的正确实例。幸运的是,这个想法可以与您使用带有上下文绑定的 class 的原始解决方法相结合(在您的情况下为 FooP):

class FooMEx[T:TypeCls] extends FooM {
  type A = T
  override val foo: String = implicitly[TypeCls[A]].foo
}

现在不要让你的 class 直接扩展 FooM,而是让它们扩展 FooMEx:

class MyFoo extends FooMEx[Int]

FooMEx 和原来的 FooP class 之间的唯一区别是 FooMEx 扩展了 FooM, 所以 MyFooFooM 的一个正确实例,因此可以与固定的 API.

一起使用

能否从第三方库中复制代码。覆盖方法就可以了。

class MyFooM extends FooM { type A = Int 
  override def foo: String = implicitly[TypeCls[A]].foo}

这是一个 hack,但我怀疑还有什么更好的。

我不知道为什么会这样。它必须是某种顺序,其中类型别名在隐式表达式中被替换。

只有语言规范方面的专家才能告诉你确切的原因。