Search code examples
scalagenericscovariance

Covariant return type implementation


I'm trying to implement a covariant return type for a method in Scala. Let's assume we have the following scenario:

class Animal
class Dog extends Animal
class Cat extends Animal

abstract class M[-T]{
  def get[U <: T](): U
}

val container = new M[Animal] {
  override def get[U <: Animal](): U = ???
}

How should I do that?


Solution

  • If you're just curious, for example you can use Shapeless

    import shapeless.{Generic, HNil}
    
    def get[U <: Animal]()(implicit generic: Generic.Aux[U, HNil]): U = 
      generic.from(HNil)
    
    get[Dog] // Dog() for case class, Dog@34340fab otherwise
    get[Cat] // Cat() for case class, Cat@2aafb23c otherwise
    get[Nothing] // doesn't compile
    get[Null] // doesn't compile
    get[Cat with Dog] // doesn't compile
    

    Or you can use a macro

    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    def get[U <: Animal](): U = macro getImpl[U]
    
    def getImpl[U: c.WeakTypeTag](c: blackbox.Context)(): c.Tree = {
      import c.universe._
      q"new ${weakTypeOf[U]}"
    }
    

    Or you can use runtime reflection

    import scala.reflect.runtime.universe._
    import scala.reflect.runtime
    
    def get[U <: Animal : TypeTag](): U = {
      val typ = typeOf[U]
      val constructorSymbol = typ.decl(termNames.CONSTRUCTOR).asMethod
      val runtimeMirror     = runtime.currentMirror
      val classSymbol       = typ.typeSymbol.asClass
      val classMirror       = runtimeMirror.reflectClass(classSymbol)
      val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
      constructorMirror().asInstanceOf[U]
    }
    

    (see also example with Java reflection in @KrzysztofAtłasik's answer.)

    Or you just can introduce a type class and define its instances manually

    trait Get[U <: Animal] {
      def get(): U
    }
    object Get {
      implicit val dog: Get[Dog] = () => new Dog
      implicit val cat: Get[Cat] = () => new Cat
    }
    
    def get[U <: Animal]()(implicit g: Get[U]): U = g.get()