简体   繁体   中英

Reflect type name in String to actual type in Scala

I've the following class-object

class DefaultValue[+A](val default: A)

object DefaultValue {

  implicit object DefaultDouble extends DefaultValue[Double](-1.0)
  implicit object DefaultFloat extends DefaultValue[Float](-1f)
  implicit object DefaultInt extends DefaultValue[Int](-1)
  implicit object DefaultBoolean extends DefaultValue[Boolean](false)

  def value[A](implicit value: DefaultValue[A]): A = value.default
}

To use this I call DefaultValue.value[Int] or DefaultValue.value[Double] etc

However, I need the ability to call DefaultValue.value[Int] when I have user input as "Int" : the typename written in String, and similarly for others.

I wish to do this without pattern-matching, as it would essentially beat the purpose of inserting TypeTag in the function. Is there a better way to do this, perhaps using Scala Reflection?

One problem with that is that you won't be able to get a good return type. Any or DefaultValue[_] is the best you can get.

You can use reflection to write something like this:

import scala.reflect.runtime.{currentMirror => cm, universe => ru}

def defaultByTypename(typename: String): DefaultValue[_] = {
  val defaultValueName = "Default" + typename.trim.capitalize
  val objectSymbol = 
    ru.typeOf[DefaultValue.type].member(ru.TermName(defaultValueName))
  cm.reflectModule(objectSymbol.asModule).instance.asInstanceOf[DefaultValue[_]]
}

This will work for implicit object s. If you want it to work with implicit val s (eg implicit val DefaultString = new DefaultValue[String]("~") ), you'd have to add code for this case:

def defaultByTypename(typename: String): DefaultValue[_] = {
  val defaultValueName = "Default" + typename.trim.capitalize
  val objectSymbol = 
    ru.typeOf[DefaultValue.type].member(ru.TermName(defaultValueName))
  val result = if (objectSymbol.isModule) {
    cm.reflectModule(objectSymbol.asModule).instance
  } else if (objectSymbol.isTerm) {
    cm.reflect(DefaultValue).reflectField(objectSymbol.asTerm).get
  } else sys.error("Unknown typename")
  result.asInstanceOf[DefaultValue[_]]
}

But actually, I think, it's not a bad idea to go for a Map with the correspondence:

val typename2DefaultValue = Map[String, DefaultValue[_]](
  "Double" -> DefaultDouble,
  "Float" -> DefaultFloat,
  "Int" -> DefaultInt,
  "Boolean" -> DefaultBoolean
)

This approach is probably faster and really not that hard to maintain. Also, it would be possible to not require direct correspondence between object name and user input, or to have several possible strings for a single DefaultValue , etc.


Also, if you declare the base class as sealed , and extend it only with object s, you could streamline the creation of this Map :

import scala.reflect.runtime.{currentMirror => cm, universe => ru}

sealed class DefaultValue[+A](val default: A)

object DefaultValue {
  implicit object DefaultDouble extends DefaultValue[Double](-1.0)
  implicit object DefaultFloat extends DefaultValue[Float](-1f)
  implicit object DefaultInt extends DefaultValue[Int](-1)
  implicit object DefaultBoolean extends DefaultValue[Boolean](false)

  val typename2DefaultValue: Map[String, DefaultValue[_]] = {
    val subclasses = ru.symbolOf[DefaultValue[_]].asClass.knownDirectSubclasses
    subclasses.map { subclass =>
      subclass.name.toString.stripPrefix("Default") ->
        cm.reflectModule(cm.staticModule(subclass.fullName)).instance.asInstanceOf[DefaultValue[_]]
    }.toMap
  }
}

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