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 (e.g. 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
}
}