Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating instances of a covariant type class from instances of a non-covariant one

Suppose I've got a simple type class whose instances will give me a value of some type:

trait GiveMeJustA[X] { def apply(): X }

And I've got some instances:

case class Foo(s: String)
case class Bar(i: Int)

implicit object GiveMeJustAFoo extends GiveMeJustA[Foo] {
  def apply() = Foo("foo")
}

implicit object GiveMeJustABar extends GiveMeJustA[Bar] {
  def apply() = Bar(13)
}

Now I have a similar (but unrelated) type class that does the same thing but is covariant in its type parameter:

trait GiveMeA[+X] { def apply(): X }

In its companion object we tell the compiler how to create instances from instances of our non-covariant type class:

object GiveMeA {
  implicit def fromGiveMeJustA[X](implicit giveMe: GiveMeJustA[X]): GiveMeA[X] =
    new GiveMeA[X] { def apply() = giveMe() }
}

Now I'd expect implicitly[GiveMeA[Foo]] to compile just fine, since there's only one way to get a GiveMeA[Foo] given the pieces we have here. But it doesn't (at least not on either 2.10.4 or 2.11.2):

scala> implicitly[GiveMeA[Foo]]
<console>:16: this.GiveMeA.fromGiveMeJustA is not a valid implicit value for GiveMeA[Foo] because:
hasMatchingSymbol reported error: ambiguous implicit values:
 both object GiveMeJustAFoo of type GiveMeJustAFoo.type
 and object GiveMeJustABar of type GiveMeJustABar.type
 match expected type GiveMeJustA[X]
              implicitly[GiveMeA[Foo]]
                        ^
<console>:16: error: could not find implicit value for parameter e: GiveMeA[Foo]
              implicitly[GiveMeA[Foo]]
                        ^

If we get rid of our irrelevant GiveMeJustA instance, it works:

scala> implicit def GiveMeJustABar: List[Long] = ???
GiveMeJustABar: List[Long]

scala> implicitly[GiveMeA[Foo]]
res1: GiveMeA[Foo] = GiveMeA$$anon$1@2a4f2dcc

This is in spite of the fact that there's no way we can apply GiveMeA.fromGiveMeJustA to this instance to get a GiveMeA[Foo] (or any subtype of GiveMeA[Foo]).

This looks like a bug to me, but it's possible that I'm missing something. Does this make any sense? Is there a reasonable workaround?

like image 552
Travis Brown Avatar asked Sep 10 '14 16:09

Travis Brown


1 Answers

I do not understand why its working, but the following code resolves the implicit successfully in the current case (at least on scala v-2.10.1). However, this still not explains why your example is not working in the first place:

We change the implicit GiveMeA[X] instance to search for implicit GiveMeJustA instances where the type parameter is bounded upwards by X, thus it searches for GiveMeJustA[_ <: X]

object GiveMeA {
  implicit def fromGiveMeJustA[X](implicit giveMe: GiveMeJustA[_ <: X]) : GiveMeA[X] =
    new GiveMeA[X] { def apply() = giveMe() }
}

We can then print out the expected output

val a = implicitly[GiveMeA[Foo]]
println(a()) // prints "Foo(foo)"

However, as soon as we introduce a new subclass

case class FooChild(s: String) extends Foo(s)

and the respective GiveMeJustA typeclass instance

implicit object GiveMeJustAFooChild extends GiveMeJustA[FooChild] {
    def apply() = FooChild("fooChild")
}

the compiler complains (as expected)

error: could not find implicit value for parameter e: GiveMeA[Foo]
    val a = implicitly[GiveMeA[Foo]]
like image 171
mucaho Avatar answered Oct 24 '22 20:10

mucaho