简体   繁体   中英

Scala - Generic implicit with lower bound

Consider the following:

class Super
class Sub extends Super

implicit val implicitOption: Option[Super] = None
def f[A, B >: A](a: A)(implicit i: Option[B]) = println("It worked")

If I call f(new Super) , it works fine, but f(new Sub) gives me a compilation error (could not find implicit value for parameter i ).

Why can't implicitOption be used as the implicit parameter when A = Sub ?

In this case, compiler needs some help figuring out which B to use. So

f[Sub, Super](new Sub)

works just fine.

--- Old answer - still relevant but approaches from different perspective --

Because Option is covariant - which means you can pass Option[Sub] where Option[Super] is expected, but not the vice versa.

To make this work, you'd need a contravariant class (let's call it ContraOption for now) - so that you can pass ContraOption[Super] where ContraOption[Sub] is expected.

Continuing on your example:

class Super
class Sub extends Super

implicit val implicitOption: Option[Super] = None

sealed abstract class ContraOption[-A]
case object ContraNone extends ContraOption[Any]
case class ContraSome[A](value: A) extends ContraOption[A]

implicit val implicitContraOption: ContraOption[Super] = ContraNone

def f[A, B >: A](a: A)(implicit i: Option[B]) = println("It worked")
def f2[A, B >: A](a: A)(implicit i: ContraOption[B]) = println("It worked 2")

f(new Super)  // It worked
f2(new Sub)   // It worked 2

Note that both trying to do f(new Sub) (as in your example) and f2(new Super) fails compilation with "implicit not found".

For more fundamental understanding of co- and contravariance, please refer to the docs . But simply put, covariant generic class "follows" the ancestor-descendent links, while contravariant classes reverse the direction. To illustrate (in pseudo-scala):

class Super
class Sub extends Super

class Covariant[+A]
class Contravariant[-A]

This gives us the following relationships:
Sub <: Super
Covariant[Sub] <: Covariant[Super]
Contravariant[Super] <: Covariant[Sub]

The solution turned out to be to move the parameter B from f to implicitOption by changing their definitions like this.

implicit def implicitOption[B <: Super]: Option[B] = None
def f[A](a: A)(implicit i: Option[A]) = println("It worked")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM