Is there any way to dynamically instantiate an Enumeration#Value in Scala?
So far I have:
object Letter extends Enumeration {
val A,B,C = Value
}
// fieldType is of type Universe.Type for the field in my case class, which happens to
// be of type Letter.Value
val ftype = fieldType.typeSymbol.name.toString
val enumVal = "B" // a valid Enumeration.Value
val erasedEnumType = fieldType.asInstanceOf[TypeRef] // Letter
Now what? I'm trying to arrive at an object of value Letter.B in this case.
I saw this clip on another posting:
def create[T <: Enum[T]](clazz: Class[T], input: String): T = Enum.valueOf(clazz, input)
I couldn't make this work because I don't have "T" at compile-time (I'm parsing this value from input strings at runtime).
You mean retrieve instead of instantiate?
scala> object Letter extends Enumeration {
| val A,B,C = Value
| }
defined module Letter
scala> Letter withName "B"
res0: Letter.Value = B
as opposed to creating another value.
Updated:
package reflectenum
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{currentMirror=>cm}
import scala.reflect.NameTransformer._
object Letters extends Enumeration {
val A,B,C = Value
}
object Test extends App {
val claas = cm.classLoader loadClass "reflectenum.Letters$"
Console println s"$claas"
val enum = claas.getField(MODULE_INSTANCE_NAME).get(null).asInstanceOf[Enumeration]
Console println s"$enum"
Console println s"${enum withName "B"}"
// given some element of the enumeration
val v = enum withName "B"
val im = cm reflect v
val outerName = newTermName("scala$Enumeration$$outerEnum")
val outer = typeOf[enum.Value] member outerName
val f = im reflectField outer.asTerm.accessed.asTerm
assert(enum == f.get)
}
This works:
val foo = Class.forName("reflectnum.Letter$") //← note extra $
val anObj = foo.getField("MODULE$").get(foo)
val meth = anObj.getClass.getMethod("withName",classOf[String])
val z = meth.invoke(anObj,"B")
Related
Suppose I have a MyItem trait and its companion object has an apply() function that creates a class instance called SubItem that extends from MyItem:
import scala.reflect.runtime.{universe => ru}
trait MyItem {
import MyItem._
def num: Option[Int]
}
object MyItem {
class SubItem(val num: Option[Int]) extends MyItem
def apply(num: Option[Int]): MyItem = new SubItem(num) // creates SubItem
}
def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]
val modifiedItem = MyItem(Some(11))
val theType = getTypeTag(modifiedItem).tpe
If you print out theType above, it will be MyItem.
At this point if you try to use reflection to modify the field num, it's not going to work because MyItem has num as a method, not a field (as in MyItem.SubItem):
val m = ru.runtimeMirror(modifiedItem.getClass.getClassLoader)
val numTermSymb = theType.decl(ru.TermName("num")).asTerm
val im = m.reflect(modifiedItem)
val numFieldMirror = im.reflectField(numTermSymb) // not going to work
numFieldMirror.get
numFieldMirror.set(Some(999)) // my goal, if possible
Unfortunately, above will throw out scala.ScalaReflectionException: expected a field or an accessor method symbol, you provided method num.
Instead you should do the following:
val numTermSymb = theType.decl(ru.TermName("num")).asMethod
val im = m.reflect(modifiedItem)
val numFieldMirror = im.reflectMethod(numTermSymb)
numFieldMirror() // returns `Some(11)`
But my goal is to access the SubItem class that extends MyItem and modify its field. How can I get an instance of the type MyItem and modify the field in MyItem.SubItem that MyItem's method num is accessing?
Replace
val theType = getTypeTag(modifiedItem).tpe
with
val theType = ru.typeOf[MyItem.SubItem]
if you know the name of class statically or with
val theType = m.staticClass("pckg.MyItem.SubItem").typeSignature
if you know the name of class dynamically.
Try
val className = modifiedItem.getClass.getName.replace('$', '.')
val theType = m.staticClass(className).typeSignature
Actually, m.staticClass(className).typeSignature is AnyRef with pckg.MyItem {...} i.e. parents/decls of SubItem
theType =:= ru.typeOf[MyItem.SubItem] // false
so, although numFieldMirror.get/set work, it's better to use toType instead of typeSignature
val className = modifiedItem.getClass.getName.replace('$', '.')
val theType = m.staticClass(className).toType
theType =:= ru.typeOf[MyItem.SubItem] // true
One more way is purely Scala-like
val instanceMirror = m.reflect(modifiedItem)
val theType = instanceMirror.symbol.toType
theType =:= ru.typeOf[MyItem.SubItem] // true
It's even better because doesn't use error-prone and implementation-dependent operations on strings (replace).
In Scala 2.10.6, using json4s 3.2.11, to which I'm constrained for now, using the following code:
object Thing1 extends Enumeration {
type Thing1 = Value
val A = Value(0, "A")
val B = Value(1, "B")
}
object Thing2 extends Enumeration {
type Thing2 = Value
val A = Value(0, "A")
val B = Value(1, "B")
val C = Value(2, "C")
}
case class ThingHolder(thing1: Thing1, thing2: Thing2)
class ThingSpec extends FlatSpec with Matchers {
"desrialized" should "match original" in {
import org.json4s.native.Serialization.writePretty
implicit val formats = DefaultFormats + new EnumNameSerializer(Thing1) + new EnumNameSerializer(Thing2)
val original = ThingHolder(Thing1.A, Thing2.C)
println(original)
val serialized = writePretty(original)
println(serialized)
val jValue = JsonParser.parse(serialized)
val deserialized = jValue.camelizeKeys.extract[ThingHolder]
println(deserialized)
println(deserialized.thing1)
deserialized should be(original)
}
}
Results in:
ThingHolder(A,C)
{
"thing1":"A",
"thing2":"C"
}
ThingHolder(A,C)
A
ThingHolder(A,C) was not equal to ThingHolder(A,C)
This occurs with Enumeration, but not another type, for example, String. If the case class has only one Enumeration, it works fine. Why is that? I can correct this by deserializing to a case class with Strings, then mapping to the case class as I want it to be. Is there a way to coax the json4s to deserialize directly such that the deserialized object matches the original?
EDIT:
Here's the hack I mentioned to do the mapping:
case class ThingHolderSerialized(thing1: String, thing2: String)
...
val deserialized = jValue.camelizeKeys.extract[ThingHolderSerialized]
val reconstituted = ThingHolder(Thing1.withName(deserialized.thing1), Thing2.withName(deserialized.thing2))
EDIT:
Actually, the separate serializer class is unnecessary:
val deserialized = jValue.camelizeKeys.extract[ThingHolder]
val reconstituted = ThingHolder(Thing1.withName(deserialized.thing1.toString), Thing2.withName(deserialized.thing2.toString))
EDIT:
It appears that it's thing1 that's not properly deserialized, since this also builds a matching reconstituted object:
val reconstituted = deserialized.copy(thing1 = Thing1.withName(deserialized.thing1.toString))
but not this:
val reconstituted = deserialized.copy(thing2 = Thing2.withName(deserialized.thing2.toString))
I'm putting this out there as a solution for people that need to support json4s case class deserialization in the presence of multiple Enumerations. It's not the answer I'm looking for, but works in the meantime:
object Thing1 extends Enumeration {
type Thing1 = Value
val A = Value(0, "A")
val B = Value(1, "B")
}
object Thing2 extends Enumeration {
type Thing2 = Value
val A = Value(0, "A")
val B = Value(1, "B")
val C = Value(2, "C")
}
case class ThingHolder(thing1: Thing1, thing2: Thing2)
class ThingSpec extends FlatSpec with Matchers {
"reconstituted" should "match original" in {
import org.json4s.native.Serialization.writePretty
implicit val formats = DefaultFormats + new EnumNameSerializer(Thing1) + new EnumNameSerializer(Thing2)
val original = ThingHolder(Thing1.A, Thing2.C)
println(original)
val serialized = writePretty(original)
println(serialized)
val jValue = JsonParser.parse(serialized)
val deserialized = jValue.camelizeKeys.extract[ThingHolder]
println(deserialized)
val reconstituted = deserialized.copy(thing1 = Thing1.withName(deserialized.thing1.toString))
reconstituted should be(original)
}
}
The key is in the line where we're building reconstituted. We make a copy, and in the process, convert thing1 to a string and back to an Enumeration. Fortunately, toString works without the field being correctly deserialized.
say i have enumeration like this
object SimpleEnum extends Enumeration{
val ONE = Value(1)
val TWO = Value(2)
val THREE = Value(3)
etc...
}
also i've a class which i want to be extending Ordered[SimpleEnum]
class SimpleClass(se: SimpleEnum) extends Ordered[SimpleEnum] {
override def compare(that: SimpleEnum): Int = this.se.[here i want to get instance of SimpleEnum, just like i used to do in java, but unfortunately it's not possible]
}
So in SimpleClass i just need to get the Value attached to corresponding SimpleEnum val.
In java i'm able to declare a variable on enum and access an actual value corresponding to it by this var, in scala i'm looking for a way to get an instance of enum and reach out for its value.
It is possible, use id method.
scala> object E extends Enumeration {
val A = Value(1)
val B = Value(7)
val C = Value(2)
}
defined object E
scala> E.A.id
res7: Int = 1
scala> E.B.id
res8: Int = 7
scala> E.C.id
res9: Int = 2
Enumeration values can also be easly compared
scala> E.A < E.B
res10: Boolean = true
scala> E.C < E.B
res11: Boolean = true
scala> E.B < E.A
res12: Boolean = false
Refer to documentation for more
Edit
Your code in the picture is wrong. Firstly, as in your original code (that is not in the picture), SimpleEnum should be an object, not a class. As soon as you make that change your code won't compile and that should ring a bell.
You want SimpleClass to be able to wrap your enum values. Type of those values (i.e. ONE, TWO, THREE) is not SimpleEnum, it is SimpleEnum.Value. Objects of this type have id method.
class SimpleClass(se: SimpleEnum.Value) extends Ordered[SimpleEnum.Value] {
override def compare(that: SimpleEnum.Value): Int = se.id
}
A common thing to do is to declare a type alias for Value with exact same name as the enum object. Then you can import this type and use it
object SimpleEnum extends Enumeration {
type SimpleEnum = Value
val ONE = Value(1)
val TWO = Value(2)
val THREE = Value(3)
}
import SimpleEnum.SimpleEnum
class SimpleClass(se: SimpleEnum) extends Ordered[SimpleEnum] {
override def compare(that: SimpleEnum): Int = se.id
}
Note that Enumeration#Value already implements Ordered[Value]. You can verify it in the docs that I linked earlier.
There is no classic java enum in scala, but because the language is so cool, it was possible to create a regular class called Enumeration that with some tricks allows for similar behavior.
The question is already answered but my approach is the following:
object SimpleEnum extends Enumeration{
type SimpleEnum = Value
val ONE = Value(1)
val TWO = Value(2)
val THREE = Value(3)
etc...
}
class SimpleClass(val i : Int){
def getNumber() {
import SimpleEnum._
SimpleEnum(i)
}
}
Given the following Enumeration...
object MyEnum extends Enumeration {
type MyEnum = Value
val Val1 = Value("val1")
val Val2 = Value("val2")
val Val3 = Value("val3")
}
import MyEnum._
... and the following Map...
val m = Map(
val1 -> "one",
val2 -> "two",
val3 -> "three"
)
... I need to transform m to JSON:
import play.api.libs.json._
val js = Json.toJson(m)
The last statement doesn't compile because the compiler doesn't find a Json serializer for type scala.collection.immutable.Map[MyEnum.Value,String].
Question: Since Play does provide a serializer for type scala.collection.immutable.Map[String,String], and my enumeration actually contains strings, is there a way to reuse the default JSON serializer?
The built-in Reads objects don't define a Reads for a Map that's parametric in the key. You could do something like:
implicit def looserMapWrites[A <: AnyRef, B : Writes]: Writes[Map[A, B]] = Writes { o =>
Json.toJson(o.map { case (key, value) =>
key.toString -> value
})(Writes.mapWrites)
}
And with your values above, you get:
scala> Json.toJson(m)(looserMapWrites)
res1: play.api.libs.json.JsValue = {"val1":"one","val2":"two","val3":"three"}
If you want to, you can tighten those bounds on A to make it not applicable to any AnyRef.
You need to define a play.api.libs.json.Format for MyEnum.Value which translates your enum to and from a string representation. Such as the following:
import play.api.libs.json._
object MyEnum extends Enumeration {
type MyEnum = Value
val Val1 = Value("val1")
val Val2 = Value("val2")
val Val3 = Value("val3")
implicit val myEnumWrites = new Writes[Value]{
def writes(o:Value)=JsString(o.toString)
}
}
a more complete format (read and write) for MyEnum could look like
implicit val myEnumFormat = new Format[Value]{
def writes(o:Value)=JsString(o.toString)
def reads(json:JsValue):JsResult[Value]=json match {
case JsString("val1") =>JsSuccess(Val1)
case JsString("val2") =>JsSuccess(Val2)
case JsString("val3") =>JsSuccess(Val3)
case other => JsError(s"$other is not a valid value for MyEnum")
}
}
Using Enumeration is not advised. It can generally be replaced by either a sealed trait and a hierarchy of case object/case classes OR by using a java enumeration.
I heard that with Dynamic it is somehow possible to do dynamic typing in Scala. But I can't imagine how that might look like or how it works.
I found out that one can inherit from trait Dynamic
class DynImpl extends Dynamic
The API says that one can use it like this:
foo.method("blah") ~~> foo.applyDynamic("method")("blah")
But when I try it out it doesn't work:
scala> (new DynImpl).method("blah")
<console>:17: error: value applyDynamic is not a member of DynImpl
error after rewriting to new DynImpl().<applyDynamic: error>("method")
possible cause: maybe a wrong Dynamic method signature?
(new DynImpl).method("blah")
^
This is completely logical, because after looking to the sources, it turned out that this trait is completely empty. There is no method applyDynamic defined and I can't imagine how to implement it by myself.
Can someone show me what I need to do to make it to work?
Scalas type Dynamic allows you to call methods on objects that don't exist or in other words it is a replica of "method missing" in dynamic languages.
It is correct, scala.Dynamic doesn't have any members, it is just a marker interface - the concrete implementation is filled-in by the compiler. As for Scalas String Interpolation feature there are well defined rules describing the generated implementation. In fact, one can implement four different methods:
selectDynamic - allows to write field accessors: foo.bar
updateDynamic - allows to write field updates: foo.bar = 0
applyDynamic - allows to call methods with arguments: foo.bar(0)
applyDynamicNamed - allows to call methods with named arguments: foo.bar(f = 0)
To use one of these methods it is enough to write a class that extends Dynamic and to implement the methods there:
class DynImpl extends Dynamic {
// method implementations here
}
Furthermore one need to add a
import scala.language.dynamics
or set the compiler option -language:dynamics because the feature is hidden by default.
selectDynamic
selectDynamic is the easiest one to implement. The compiler translates a call of foo.bar to foo.selectDynamic("bar"), thus it is required that this method has an argument list expecting a String:
class DynImpl extends Dynamic {
def selectDynamic(name: String) = name
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#6040af64
scala> d.foo
res37: String = foo
scala> d.bar
res38: String = bar
scala> d.selectDynamic("foo")
res54: String = foo
As one can see, it is also possible to call the dynamic methods explicitly.
updateDynamic
Because updateDynamic is used to update a value this method needs to return Unit. Furthermore, the name of the field to update and its value are passed to different argument lists by the compiler:
class DynImpl extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) =
map get name getOrElse sys.error("method not found")
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#7711a38f
scala> d.foo
java.lang.RuntimeException: method not found
scala> d.foo = 10
d.foo: Any = 10
scala> d.foo
res56: Any = 10
The code works as expected - it is possible to add methods at runtime to the code. On the other side, the code isn't typesafe anymore and if a method is called that doesn't exist this must be handled at runtime as well. In addition this code is not as useful as in dynamic languages because it is not possible to create the methods that should be called at runtime. This means that we can't do something like
val name = "foo"
d.$name
where d.$name would be transformed to d.foo at runtime. But this is not that bad because even in dynamic languages this is a dangerous feature.
Another thing to note here, is that updateDynamic needs to be implemented together with selectDynamic. If we don't do this we will get a compile error - this rule is similar to the implementation of a Setter, which only works if there is a Getter with the same name.
applyDynamic
The ability to call methods with arguments is provided by applyDynamic:
class DynImpl extends Dynamic {
def applyDynamic(name: String)(args: Any*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#766bd19d
scala> d.ints(1, 2, 3)
res68: String = method 'ints' called with arguments '1', '2', '3'
scala> d.foo()
res69: String = method 'foo' called with arguments ''
scala> d.foo
<console>:19: error: value selectDynamic is not a member of DynImpl
The name of the method and its arguments again are separated to different parameter lists. We can call arbitrary methods with an arbitrary number of arguments if we want but if we want to call a method without any parentheses we need to implement selectDynamic.
Hint: It is also possible to use apply-syntax with applyDynamic:
scala> d(5)
res1: String = method 'apply' called with arguments '5'
applyDynamicNamed
The last available method allows us to name our arguments if we want:
class DynImpl extends Dynamic {
def applyDynamicNamed(name: String)(args: (String, Any)*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#123810d1
scala> d.ints(i1 = 1, i2 = 2, 3)
res73: String = method 'ints' called with arguments '(i1,1)', '(i2,2)', '(,3)'
The difference in the method signature is that applyDynamicNamed expects tuples of the form (String, A) where A is an arbitrary type.
All of the above methods have in common that their parameters can be parameterized:
class DynImpl extends Dynamic {
import reflect.runtime.universe._
def applyDynamic[A : TypeTag](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
args.asInstanceOf[Seq[Int]].sum.asInstanceOf[A]
case "concat" if typeOf[A] =:= typeOf[String] =>
args.mkString.asInstanceOf[A]
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#5d98e533
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
Luckily, it is also possible to add implicit arguments - if we add a TypeTag context bound we can easily check the types of the arguments. And the best thing is that even the return type is correct - even though we had to add some casts.
But Scala would not be Scala when there is no way to find a way around such flaws. In our case we can use type classes to avoid the casts:
object DynTypes {
sealed abstract class DynType[A] {
def exec(as: A*): A
}
implicit object SumType extends DynType[Int] {
def exec(as: Int*): Int = as.sum
}
implicit object ConcatType extends DynType[String] {
def exec(as: String*): String = as.mkString
}
}
class DynImpl extends Dynamic {
import reflect.runtime.universe._
import DynTypes._
def applyDynamic[A : TypeTag : DynType](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
implicitly[DynType[A]].exec(args: _*)
case "concat" if typeOf[A] =:= typeOf[String] =>
implicitly[DynType[A]].exec(args: _*)
}
}
While the implementation doesn't look that nice, its power can't be questioned:
scala> val d = new DynImpl
d: DynImpl = DynImpl#24a519a2
scala> d.sum(1, 2, 3)
res89: Int = 6
scala> d.concat("a", "b", "c")
res90: String = abc
At the top of all, it is also possible to combine Dynamic with macros:
class DynImpl extends Dynamic {
import language.experimental.macros
def applyDynamic[A](name: String)(args: A*): A = macro DynImpl.applyDynamic[A]
}
object DynImpl {
import reflect.macros.Context
import DynTypes._
def applyDynamic[A : c.WeakTypeTag](c: Context)(name: c.Expr[String])(args: c.Expr[A]*) = {
import c.universe._
val Literal(Constant(defName: String)) = name.tree
val res = defName match {
case "sum" if weakTypeOf[A] =:= weakTypeOf[Int] =>
val seq = args map(_.tree) map { case Literal(Constant(c: Int)) => c }
implicitly[DynType[Int]].exec(seq: _*)
case "concat" if weakTypeOf[A] =:= weakTypeOf[String] =>
val seq = args map(_.tree) map { case Literal(Constant(c: String)) => c }
implicitly[DynType[String]].exec(seq: _*)
case _ =>
val seq = args map(_.tree) map { case Literal(Constant(c)) => c }
c.abort(c.enclosingPosition, s"method '$defName' with args ${seq.mkString("'", "', '", "'")} doesn't exist")
}
c.Expr(Literal(Constant(res)))
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl#c487600
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
scala> d.noexist("a", "b", "c")
<console>:11: error: method 'noexist' with args 'a', 'b', 'c' doesn't exist
d.noexist("a", "b", "c")
^
Macros give us back all compile time guarantees and while it is not that useful in the above case, maybe it can be very useful for some Scala DSLs.
If you want to get even more information about Dynamic there are some more resources:
The official SIP proposal that introduced Dynamic into Scala
Practical uses of a Dynamic type in Scala - another question on SO (but very outdated)
kiritsuku's answer is better. This is to show a practical TL;DR use case.
Dynamics can be used to dynamically construct an object with the builder pattern.
import scala.language.dynamics
case class DynImpl(
inputParams: Map[String, List[String]] = Map.empty[String, List[String]]
) extends Dynamic {
def applyDynamic(name: String)(args: String*): DynImpl = {
copy(inputParams = inputParams ++ Map(name -> args.toList))
}
}
val d1 = DynImpl().whatever("aaa", "bbb").cool("ccc")
println(d1.inputParams) // Map(whatever -> List(aaa, bbb), cool -> List(ccc))
val d2 = DynImpl().whatever("aaa", "bbb").fun("haha")
println(d2.inputParams) // Map(whatever -> List(aaa, bbb), fun -> List(haha))