简体   繁体   中英

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?

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()

Alternatively, you could use reflection to create new instances. You just need to get implicit classTag :

import scala.reflect.ClassTag

class Animal
class Dog extends Animal
class Cat extends Animal

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

val container = new M[Animal] {
  override def get[U <: Animal](implicit ct: ClassTag[U]): U =
    //it gets no argument constructor so obviosly it will only work if your classes has no params
    ct.runtimeClass.getConstructor().newInstance().asInstanceOf[U]
}

container.get[Dog]

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