Scala: Implicit evidence for class with type parameter

后端 未结 3 1528
清歌不尽
清歌不尽 2021-02-14 07:20

Here is a simple setup with two traits, a class with a covariant type parameter bounded by the previous traits, and a second class with a type parameter bounded by the other cla

相关标签:
3条回答
  • 2021-02-14 07:34

    The call bar.readField is possible because the evidence instance <:< allows an implicit conversion from B to Bar[ReadableFoo].

    The problem I think that to call readField you need a successive evidence parameter F <:< ReadableFoo. So my guess is, the compiler doesn't fully substitute the type parameter of Bar in the first search stage of the implicit resolution (because to find readField, it just requires any Bar in the first place). And then it chokes on the second implicit resolution, because there is no form of 'backtracking' as far as I know.

    Anyway. The good thing is, you know more than the compiler and you can engage the conversion explicitly, either by using the apply method of <:<, or by using the helper method 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
    }
    

    There is another possibility which might be the cleanest, as it doesn't rely on the implementation of <:< which might be a problem as @Kaito suggests:

    case class Grill[+B <: Bar[_]](bar: B) {
      def readField(implicit evidence: B <:< Bar[ReadableFoo]) =
         (bar: Bar[ReadableFoo]).readField
    }
    
    0 讨论(0)
  • 2021-02-14 07:42

    0__'s answer (use the implicit evidence argument to turn bar into the right type) is the answer I'd give to the specific question you asked (although I'd suggest not using implicitly if you've got the implicit argument sitting right there).

    It's worth noting that the situation you're describing sounds like it might be a good use case for ad-hoc polymorphism via type classes, however. Say for example that we've got the following setup:

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

    And a type class, along with some convenience methods for creating new instances and for pimping a readField method onto any type that has an instance in scope:

    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
    

    And a couple of instances:

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

    And finally a readable Foo:

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

    This allows us to do the following, for example:

    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
                  ^
    

    Which is what we want.

    0 讨论(0)
  • 2021-02-14 07:45

    This is not an answer to the question, but to show that the 'type constraint' is really just an implicit conversion:

    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;
    ...
    
    0 讨论(0)
提交回复
热议问题