【问题标题】:Context bounds for type members or how to defer implicit resolution until member instantiation类型成员的上下文边界或如何将隐式解析推迟到成员实例化
【发布时间】:2015-11-18 14:04:26
【问题描述】:

在下面的示例中,有没有办法避免隐式解析选择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

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

用户应该混合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
}

【问题讨论】:

    标签: scala implicit type-members


    【解决方案1】:

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

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

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

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

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

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

    class MyFoo extends FooMEx[Int]
    

    FooMEx 和你原来的FooP 类之间的唯一区别是FooMEx 确实 扩展FooM,所以MyFooFooM 的正确实例,因此可以与固定 API 一起使用。

    【讨论】:

    • 抱歉,我注意到外部 API 使用 val 而不是 def,所以这是不可能的...
    • 哦,我的错,不知何故,我设法错过了您无法修改 FooM 的事实。那么还有另一种解决方案,请参阅我的更新。
    • 好的,谢谢,我有点担心它只是坏了。顺便提一句。我正在谈论的 API 是 ScalaCheck,它有一个默认的 Shrink 实例,它什么都不做。
    • ScalaCheck?这有点令人惊讶。那么如果TypeCls实际上是Shrink,那么FooM 是什么?
    • TypeCls 是ShrinkFooMCommandstype Atype State,我想做的是缩小State 内部的private implicit val shrinkActions (github.com/rickynils/scalacheck/blob/master/src/main/scala/org/…)
    【解决方案2】:

    你能从第三方库复制代码吗?覆盖该方法就可以了。

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

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

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

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

    【讨论】:

    • 我不得不编辑我的问题,因为 fooval 而不是 def,所以我没有隐式参数列表...
    • 他没有使用任何隐式参数列表。上面提出的解决方案仍然适用于 val,您只需将 override def 更改为 override val
    • 完全正确,我把它弄混了,这按预期工作,但不知道的一点是,在实际 API 中,val 也是private,所以没有覆盖。跨度>
    • 只需删除覆盖关键字。 val foo: String = implicitly[TypeCls[A]].foo} 这也有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-18
    相关资源
    最近更新 更多