Scala:类型参数类的隐式证据

drh*_*gen 10 scala implicit-conversion type-parameter

这是一个带有两个特征的简单设置,一个具有由前一个特征限定的协变类型参数的类,另一个类具有由另一个类限定的类型参数.对于这两个类,只有当两个特征中的一个作为类型参数的基础时,才能使用特定方法(通过隐式证据).编译好:

trait Foo
trait ReadableFoo extends Foo {def field: Int}

case class Bar[+F <: Foo](foo: F) {
  def readField(implicit evidence: F <:< ReadableFoo) = foo.field
}

case class Grill[+F <: Foo, +B <: Bar[F]](bar: B) {
  def readField(implicit evidence: F <:< ReadableFoo) = bar.readField
}
Run Code Online (Sandbox Code Playgroud)

但是,由于Bar是协变的F,我不应该需要F参数Grill.我应该要求它B是一个子类型Bar[ReadableFoo].但是,这失败了:

case class Grill[+B <: Bar[_]](bar: B) {
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField
}
Run Code Online (Sandbox Code Playgroud)

有错误:

error: Cannot prove that Any <:< this.ReadableFoo.
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField
Run Code Online (Sandbox Code Playgroud)

为什么没有考虑隐含证据?

Tra*_*own 7

0 __的答案(使用隐式证据参数bar变成正确的类型)是我给你提出的具体问题的答案(尽管implicitly如果你有隐含的参数坐在那里我建议不要使用).

值得注意的是,您所描述的情况听起来似乎可能是通过类型类的ad-hoc多态性的一个很好的用例.比如说我们有以下设置:

trait Foo
case class Bar[F <: Foo](foo: F)
case class Grill[B <: Bar[_]](bar: B)
Run Code Online (Sandbox Code Playgroud)

还有一个类型类,以及一些用于创建新实例和将readField方法移植到具有范围内实例的任何类型的便捷方法:

trait Readable[A] { def field(a: A): Int }

object Readable {
  def apply[A, B: Readable](f: A => B) = new Readable[A] {
    def field(a: A) = implicitly[Readable[B]].field(f(a))
  }

  implicit def enrich[A: Readable](a: A) = new {
    def readField = implicitly[Readable[A]].field(a)
  }
}

import Readable.enrich
Run Code Online (Sandbox Code Playgroud)

还有几个例子:

implicit def barInstance[F <: Foo: Readable] = Readable((_: Bar[F]).foo)
implicit def grillInstance[B <: Bar[_]: Readable] = Readable((_: Grill[B]).bar)
Run Code Online (Sandbox Code Playgroud)

最后是可读的Foo:

case class MyFoo(x: Int) extends Foo

implicit object MyFooInstance extends Readable[MyFoo] {
  def field(foo: MyFoo) = foo.x
}
Run Code Online (Sandbox Code Playgroud)

这允许我们执行以下操作,例如:

scala> val readableGrill = Grill(Bar(MyFoo(11)))
readableGrill: Grill[Bar[MyFoo]] = Grill(Bar(MyFoo(11)))

scala> val anyOldGrill = Grill(Bar(new Foo {}))
anyOldGrill: Grill[Bar[java.lang.Object with Foo]] = Grill(Bar($anon$1@483457f1))

scala> readableGrill.readField
res0: Int = 11

scala> anyOldGrill.readField
<console>:22: error: could not find implicit value for evidence parameter of
type Readable[Grill[Bar[java.lang.Object with Foo]]]
              anyOldGrill.readField
              ^
Run Code Online (Sandbox Code Playgroud)

这就是我们想要的.


0__*_*0__ 6

调用bar.readField是可能的,因为证据实例<:<允许隐式转换BBar[ReadableFoo].

我认为打电话给readField你的问题需要一个连续的证据参数F <:< ReadableFoo.所以我的猜测是,编译器并没有完全替换Bar隐式分辨率的第一个搜索阶段中的类型参数(因为要查找readField,它首先只需要任何Bar一个).然后它扼杀了第二个隐含的解决方案,因为据我所知,没有任何形式的"回溯".

无论如何.好处是,您知道的不仅仅是编译器,您可以通过使用apply方法<:<或使用辅助方法显式地进行转换implicitly:

case class Grill[+B <: Bar[_]](bar: B) {
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) = evidence(bar).readField
}

case class Grill[+B <: Bar[_]](bar: B) {
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) = 
    implicitly[Bar[ReadableFoo]](bar).readField
}
Run Code Online (Sandbox Code Playgroud)

还有另一种可能是最干净的可能性,因为它不依赖于实施<:<可能是一个问题,因为@Kaito建议:

case class Grill[+B <: Bar[_]](bar: B) {
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) =
     (bar: Bar[ReadableFoo]).readField
}
Run Code Online (Sandbox Code Playgroud)