简体   繁体   中英

Decide Type Parameter on runtime in Scala

For example I have code looks like this:

class Parent
class Child1 extends Parent
class Child2 extends Parent

class Foo {

  def retrieve(arg: String): List[Parent] = {
    arg match {
      case "Child1" => get[Child1]()
      case "Child2" => get[Child2]()
    }
  }

  def get[T: Manifest](): List[T] = ...

}

In the retrieve method, I want to simplify the code into one get method call only like this:

  def retrieve(arg: String): List[Parent] = {
    val t = arg match {
      case "Child1" => ?
      case "Child2" => ?
    }

    get[t]()
  }

Is it possible to achieve this in scala?

UPDATE:

I tried the solution from the answer here but I got a problem, it doesn't work with overloaded get method, for example:

def get[T: Manifest](x: String): List[T] = ...

def get[T: Manifest, U: Manifest](x: String): List[(T, U)] = ...

For example, in the retrieve :

val t = arg match {
  case "Child1" => manifest[Child1]
  case "Child2" => manifest[Child2]
}

get("test")(t)

I got ambiguous reference to overloaded definition compile error on the line get("test")(t) .

Your question boils down to how to retrieve the Manifest of a given type. This can be done using the manifest method. Then you can explictly pass the manifest, to get .

class Foo {
  def retrieve(arg: String): List[Parent] = {
    val t = arg match {
      case "Child1" => manifest[Child1]
      case "Child2" => manifest[Child2]
    }

    get(t)
  }

  def get[T <: Parent: Manifest]: List[T] = ...
}

As a side note, you should probably use a map to retrieve the manifests (rather than pattern matching) so as to make it more easily editable, or possibly at one point replacing the hard-coded list of types with some init-time computation:

object Foo {
  private val manifestByName = Map[String, Manifest[_<:Parent]](
    "Child1" -> manifest[Child1],
    "Child2" -> manifest[Child2]
  )
}
class Foo {
  def retrieve(arg: String): List[Parent] = {
    val t = Foo.manifestByName.getOrElse(arg, sys.error(s"Invalid class name $arg"))
    get(t)
  }

  def get[T <: Parent: Manifest]: List[T] = { println(manifest[T]); Nil }
}

Finally, note that Manifest is now deprecated, it was superseded with ClassTag \\ TypeTag .

Manifest is basically deprecated . :

In Scala 2.10, scala.reflect.ClassManifests are deprecated, and it is planned to deprecate scala.reflect.Manifest in favor of TypeTags and ClassTags in an upcoming point release. Thus, it is advisable to migrate any Manifest-based APIs to use Tags.

You should consider using the more modern ClassTag or TypeTag . In this case, ClassTag works better (since TypeTags can't be used in pattern matching):

def retrieve(arg: String): List[Parent] = {
    val t = arg match {
      case "Child1" => classTag[Child1]
      case "Child2" => classTag[Child2]
    }

    get(t)
}

def get[T : ClassTag]: List[T] = list collect { 
    case x: T => x 
}

You can read more about ClassTags , TypeTags , and their relationship to Manifest in the docs here .

In case it's not clear, this works because the type constraint on T is a context bound , meaning the method signature of get is equivalent to:

def get[T](implicit ev: ClassTag[T]): List[T]

So, when we call get(t) , we're explicitly specifying the implicit parameter. Read more about context bounds here .

If the context bound or implicit parameter is confusing, you can also achieve your goals by making get non-generic:

def get(c: ClassTag[_]) = list collect { 
    case x if ClassTag(x.getClass) == c => x 
}

This non-generic, non-implicit version might help you resolve your overloading issue.

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