简体   繁体   中英

Type class and implicits with singleton type/case object

I am trying to implement a type class that works with case objects instead of classes. It kind of works. When I pass the case object itself to the function it works, however, when I try to pass an object with the type of the base trait it doesn't compile.

object Test {

  sealed trait MyType
  case object Type1 extends MyType
  case object Type2 extends MyType

  trait Builder[A] {
    def build: String
  }

  object Builder {
    implicit  val type1Builder: Builder[Type1.type] = new Builder[Type1.type] {
      def build: String = s"building1"
    }

    implicit val type2Builder: Builder[Type2.type] = new Builder[Type2.type] {
      def build: String = s"building2"
    }

    def build[A](a: A)(implicit builder: Builder[A]) = builder.build
  }

  import Builder._

  // Compiles
  def test[T <: MyType](t:Type2.type): Unit = {
    println(Builder.build(t))
  }
  // Doesn't compile - 'could not find implicit value for parameter builder ' 
  def test2[T <: MyType](t:MyType): Unit = {
    println(Builder.build(t))
  }
}

That's because type parameters in scala are Invariant by default, this means that:

Builder[Type1.type] is not a subtype of Builder[MyType].

In this block of your code, you need a Builder[MyType], and neither type1Builder nor type2Builder are subtypes of Builder[MyType]:

def test[T <: MyType](t:MyType): Unit = {
    println(Builder.build(t))
}

You can make Builder's type parameter covariant (Builder[+A]), but then, both type1Builder and type2Builder will be candidates for that implicit, so it will fail again.

What you need to do is use a Context bound in your test method, instead of an upper type bound, as follows:

def test[T : Builder](t: T): Unit = {
   println(Builder.build(t))
}

This means that test receives a T type, that is a member of Builder type class, both Type1 and Type2 are members of Builder type class since there is a Builder[Type1.type] and a Builder[Type2.type] in implicit scope.

If you also want to limit test so you can only call it with implementations of MyType , you can use both an upper type bound and a context bound:

def test[T <: MyType : Builder](t: T): Unit = {
    println(Builder.build(t))
}


test(Type1) // building1
test(Type2) // building2

Somehow this worked for me. Not sure why

 def test2[T <: MyType : Builder ](t2:T): Unit = {

    println(Builder.build(t2))
  }

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