I'm trying to create an enum-like type in Scala with a generic type, later doing operations on the instances of the type that depend on what the generic is using Scala's reflect.ClassTag
to get information on the generic type. Something like this:
import scala.reflect.ClassTag
sealed trait X[T : ClassTag] {
def value: T
}
case object I1 extends X[Int] { override def value = 1 }
case object I2 extends X[Int] { override def value = 2 }
case object Sa extends X[String] { override def value = "a" }
case object Sb extends X[String] { override def value = "b" }
val values = IndexedSeq(I1, I2, Sa, Sb)
values.foreach{
case i: X[Int] => println(s"${i.value} => ${i.value + 1}")
case s: X[String] => println(s"${s.value} => ${s.value.toUpperCase}")
}
This produces the following warnings:
the type test for Playground.X[Int] cannot be checked at runtime
the type test for Playground.X[String] cannot be checked at runtime
For completeness, when run, it produces the following output (which is reasonable given the warnings):
1 => 2
2 => 3
java.lang.ExceptionInInitializerError
at Main$.<clinit>(main.scala:24)
at Main.main(main.scala)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at sbt.Run.invokeMain(Run.scala:143)
at sbt.Run.execute$1(Run.scala:93)
at sbt.Run.$anonfun$runWithLoader$5(Run.scala:120)
at sbt.Run$.executeSuccess(Run.scala:186)
at sbt.Run.runWithLoader(Run.scala:120)
at sbt.Run.run(Run.scala:127)
at com.olegych.scastie.sbtscastie.SbtScastiePlugin$$anon$1.$anonfun$run$1(SbtScastiePlugin.scala:38)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
at sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:17)
at sbt.ScastieTrapExit$App.run(ScastieTrapExit.scala:259)
at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
at Playground$.$anonfun$1(main.scala:17)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
at scala.collection.immutable.Vector.foreach(Vector.scala:1856)
at Playground$.<clinit>(main.scala:20)
... 17 more
I also tried doing it like this, with the singleton object
s implemented as instances of case class
es instead:
import scala.reflect.ClassTag
sealed trait X[T : ClassTag] {
def value: T
}
case class I(value: Int) extends X[Int]
case class S(value: String) extends X[String]
val values = IndexedSeq(I(1), I(2), S("a"), S("b"))
values.foreach{
case i: X[Int] => println(s"${i.value} => ${i.value + 1}")
case s: X[String] => println(s"${s.value} => ${s.value.toUpperCase}")
}
But I get pretty much the exact same result.
When I do something that appears to me to be similar using a Scala Array
, it works:
val values = IndexedSeq(
Array(1, 2),
Array(3, 4),
Array("a", "b"),
Array("c", "d")
)
values.foreach{
case i: Array[Int] => println(s"""${i.mkString(",")} => ${i.map(_ * 2).mkString(",")}""")
case s: Array[String] => println(s"""${s.mkString(",")} => ${s.map(_.toUpperCase).mkString(",")}""")
}
This produces no warnings and the correct output:
1,2 => 2,4
3,4 => 6,8
a,b => A,B
c,d => C,D
What am I doing wrong here? I thought ClassTag
was supposed to preserve information about a generic type during runtime? I've seen that reflect.runtime.universe.TypeTag
might be better, but the package containing that doesn't seem to be available in Scala 3, and somehow Array
is able to do what I want anyway.
Aucun commentaire:
Enregistrer un commentaire