10

这是一个具有两个特征的简单设置,一个具有由先前特征限制的协变类型参数的类,以及具有由另一个类限制的类型参数的第二个类。对于这两个类,只有当两个特征之一是类型参数的基础时,一个特定的方法才可用(通过隐式证据)。这编译得很好:

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
}

但是,由于Bar在 中是协变的F,我不应该需要在 中的F参数Grill。我应该只要求它BBar[ReadableFoo]. 然而,这失败了:

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

出现错误:

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

为什么不考虑隐含的证据?

4

3 回答 3

7

0__ 的答案(使用隐含的证据参数bar转换为正确的类型)是我对您提出的具体问题的回答(尽管implicitly如果您有隐含的参数,我建议不要使用)。

值得注意的是,您所描述的情况听起来可能是通过类型类实现临时多态性的一个很好的用例。例如,假设我们有以下设置:

trait Foo
case class Bar[F <: Foo](foo: F)
case class Grill[B <: Bar[_]](bar: B)

还有一个类型类,以及一些方便的方法,用于创建新实例和将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

还有几个例子:

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

最后是可读的Foo

case class MyFoo(x: Int) extends Foo

implicit object MyFooInstance extends Readable[MyFoo] {
  def field(foo: MyFoo) = foo.x
}

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

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
              ^

这就是我们想要的。

于 2012-07-01T23:39:48.303 回答
6

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

我认为打电话给readField你的问题需要一个连续的证据参数F <:< ReadableFoo。所以我的猜测是,编译器并没有完全替换Bar隐式解析的第一个搜索阶段的类型参数(因为 find readField,它首先需要 any 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
}

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

case class Grill[+B <: Bar[_]](bar: B) {
  def readField(implicit evidence: B <:< Bar[ReadableFoo]) =
     (bar: Bar[ReadableFoo]).readField
}
于 2012-07-01T20:23:42.073 回答
1

这不是问题的答案,而是表明“类型约束”实际上只是一种隐式转换:

Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_33).
Type in expressions to have them evaluated.
Type :help for more information.

scala> trait A { def test() {} }
defined trait A

scala> class WhatHappens[T] { def test(t: T)(implicit ev: T <:< A) = t.test() }
defined class WhatHappens

scala> :javap -v WhatHappens
...
public void test(java.lang.Object, scala.Predef$$less$colon$less);
  Code:
   Stack=2, Locals=3, Args_size=3
   0:   aload_2
   1:   aload_1
   2:   invokeinterface #12,  2; //InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
   7:   checkcast   #14; //class A
   10:  invokeinterface #17,  1; //InterfaceMethod A.test:()V
   15:  return
...
  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      16      0    this       LWhatHappens;
   0      16      1    t       Ljava/lang/Object;
   0      16      2    ev       Lscala/Predef$$less$colon$less;
...
于 2012-07-02T00:09:41.350 回答