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

Mar*_*189 5 scala implicit type-members

在下面的例子中,有没有办法避免隐式分辨率选择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}")
}
Run Code Online (Sandbox Code Playgroud)

实际产量:

With type parameter: integer
With type member:    default
Run Code Online (Sandbox Code Playgroud)

期望的输出:

With type parameter: integer
With type member:    integer
Run Code Online (Sandbox Code Playgroud)

我正在使用第三方库,该库使用上述方案为类型类提供"默认"实例TypeCls.我认为上面的代码是一个演示我的问题的最小例子.

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

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

对于类型成员,是否有相同的方法来执行此操作?

编辑:我的简化中有一个错误,实际上foo不是一个def但是a 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
}
Run Code Online (Sandbox Code Playgroud)

Rég*_*les 1

在这种特定情况下,最简单的解决方案是让其foo本身需要 的隐式实例TypeCls[A]。唯一的缺点是它会在每次调用时传递,foo而不是在实例化时传递 FooM。因此,您必须确保它们在每次调用时都在范围内foo。尽管只要TypeCls实例位于伴生对象中,您就不需要做任何特殊的事情。

trait FooM {
  type A
  def foo(implicit e: TypeCls[A]): String = e.foo
}
Run Code Online (Sandbox Code Playgroud)

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

坏消息是你使用的 API 根本就坏了。wille不可能FooM.foo返回任何有用的东西(无论 的实际值如何,它总是会解析TypeCls[A]为)。唯一的出路是在已知实际值的派生类中重写,以便能够使用正确的实例。幸运的是,这个想法可以与使用具有上下文绑定的类的原始解决方法结合起来(在您的情况下):TypeCls.defaultInstanceAfooATypeClsFooP

class FooMEx[T:TypeCls] extends FooM {
  type A = T
  override val foo: String = implicitly[TypeCls[A]].foo
}
Run Code Online (Sandbox Code Playgroud)

现在,不要让您的类直接扩展,而是FooM让它们扩展FooMEx

class MyFoo extends FooMEx[Int]
Run Code Online (Sandbox Code Playgroud)

FooMEx和你的原始类之间的唯一区别FooP是它FooMEx 确实扩展了FooM,所以MyFoo是一个适当的实例FooM,因此可以与固定的API一起使用。