I'm using scala reflections to get all fields of a case class that are not methods. I then want to see if the type is a primitive type or string (or an option of these things). Here's a simple working example that checks if a field is a String.
scala> case class SimpleCase(val1: String)
defined class SimpleCase
scala> val members = typeOf[SimpleCase].members.filter(!_.isMethod).toList
members: List[reflect.runtime.universe.Symbol] = List(value val1)
scala> members.head.typeSignature
res57: reflect.runtime.universe.Type = String
scala> members.head.typeSignature == typeOf[String]
res58: Boolean = true
Works exactly the way that I would expect it to. Here's where things get weird - when I do the same thing with an Int, or any primitive type for that matter, the type checking test fails. Here's an example of said failure:
scala> case class SimpleCase(val1: Int)
defined class SimpleCase
scala> val members = typeOf[SimpleCase].members.filter(!_.isMethod).toList
members: List[reflect.runtime.universe.Symbol] = List(value val1)
scala> members.head.typeSignature
res59: reflect.runtime.universe.Type = scala.Int
scala> members.head.typeSignature == typeOf[Int]
res60: Boolean = false
scala> members.head.typeSignature == typeOf[scala.Int]
res61: Boolean = false
Also, typeOf[Int] prints out
scala> typeOf[Int]
res62: reflect.runtime.universe.Type = Int
The type signature of SimpleCase's val1 states that it is a scala.Int rather than typeOf[Int]'s type signature, which is Int. So I tried comparing val1 to both typeOf[Int] and typeOf[scala.Int] (even though I'm pretty sure they're the same thing) to no avail.
What's going on here? Is there a way around this?
You should compare types with =:= instead of ==.
The documentation says:
Type Equality can be checked with =:=. It's important to note that == should not be used to compare types for equality-- == can't check for type equality in the presence of type aliases, while =:= can.
Related
How come this compiles:
scala> val x: Vector[Int] = Vector(1,2,3)
val x: Vector[Int] = Vector(1, 2, 3)
scala> x.contains("hello")
val res4: Boolean = false
scala> x.contains(List(Map("anything" -> 3.14)))
val res5: Boolean = false
EDIT:
This is separate from this other question in which the element type of the collection is inferred (to Any) whereas here it is explicitly set (to Int).
The signature is (in 3.0.0; I recommend always including the language/library version in such questions, and for Scala in particular)
def contains[A1 >: A](elem: A1): Boolean
This means that the argument type must be a supertype of Int, and "hello" is inferred to be Any which is indeed such a supertype.
The reason for the signature instead of maybe more expected
def contains(elem: A): Boolean
is that that signature wouldn't allow Vector and other collections to be covariant.
Specifically, if a Vector[Int] extends Vector[Any], and you can call contains(Any) on a Vector[Any], you must also be able to call it on a Vector[Int].
This question already has answers here:
How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?
(11 answers)
Closed 6 years ago.
I'm tying to write a generic function that will convert any type to Option[T]
but somehow it's not working as expected
scala> def toOption[T](obj:Any):Option[T] = obj match {
| case obj:T => Some(obj)
| case _ => None
| }
<console>:11: warning: abstract type pattern T is unchecked since it is eliminated by erasure
case obj:T => Some(obj)
^
toOption: [T](obj: Any)Option[T]
here it's seems ok it return the Option[String]
scala> toOption[String]("abc")
res0: Option[String] = Some(abc)
but here it return Some(def) instead of None
scala> toOption[Int]("def")
res1: Option[Int] = Some(def)
i can't seem to figure the appropriate way to create this generic function ,nor to understand why is that happens, I've already read many posts and questions about type erasure in scala but still can't get it, specific explanation would be a great help!
The type T is not available at runtime because Scala uses type erasure for generics like Java does. So your test case obj:T has not the desired effects and the compiler warned you about that fact.
You may use ClassTags to make the type information available at runtime. ClassTag already implements the conversion to Optional in its unapply method:
scala> import scala.reflect.{classTag,ClassTag};
import scala.reflect.{classTag, ClassTag}
scala> def toOption[T: ClassTag](obj: Any): Option[T] = classTag[T].unapply(obj);
toOption: [T](obj: Any)(implicit evidence$1: scala.reflect.ClassTag[T])Option[T]
scala> toOption[Int](1)
res1: Option[Int] = Some(1)
scala> toOption[String](1)
res2: Option[String] = None
scala> toOption[String]("one")
res3: Option[String] = Some(one)
scala> toOption[Int]("one")
res4: Option[Int] = None
But this doesn't work for generic types as you see here:
scala> toOption[List[Int]](List("one", "two"))
res5: Option[List[Int]] = Some(List(one, two))
scala> res5.get
res6: List[Int] = List(one, two)
scala> res6(0) + res6(1)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
... 29 elided
You might want to have two type arguments of the function - the desired type and the actual type:
def toOption[Desired, Actual](obj:Actual):Option[Desired] = ???
And then, I guess, you want to return Some only if the types match:
def toOption[Desired, Actual](obj:Actual)
(implicit ev: Actual =:= Desired = null):Option[Desired] =
if(ev == null)
None
else
Some(ev(obj))
Here you have two type arguments, that might be inconvenient sometimes, when Scala cannot infer both of the arguments. For the same syntax as you used (giving single type argument), you may use the following trick:
class toOptionImpl[Desired] {
def apply[Desired, Actual](obj:Actual)
(implicit ev: Actual =:= Desired = null):Option[Desired] =
if(ev == null)
None
else
Some(ev(obj))
}
def toOption[Desired] = new toOptionImpl[Desired]
It might be used the same way:
toOption[String]("def") == Some("def")
toOption[Int]("def") == None
(You might also look into Miles Sabin's polymorphic functions https://milessabin.com/blog/2012/04/27/shapeless-polymorphic-function-values-1/, https://milessabin.com/blog/2012/05/10/shapeless-polymorphic-function-values-2/)
If A is a TypeTag the following condition will try to match the inner types of Tuple2 too, i.e String and Int.
if (typeOf[A] == typeOf[Int])
...
else if (typeOf[A] == typeOf[Tuple2[String, Int]])
...
If A is a ClassTag the following condition will match up to Tuple2 but not the inner types.
if (classTag[A] == classTag[Int])
...
else if (classTag[A] == classTag[Tuple2[String, Int]])
...
Is there a way to match the inner types of a tuple using ClassTag?
With a ClassTag, no. A ClassTag only stores information about the class. In Tuple2[A, B], the type parameters A and B are not class information, they are type information. That is, Tuple2 is the class, and Tuple2[A, B] is the type. Thus TypeTag should be used to compare. Actually, the Type contained in the TypeTag should be used to compare.
Therefore, if A = (A1, A2), then classTag[A] will only know that A is Tuple2, but will know nothing of A1 and A2.
Another note, when comparing type tags you should use =:=, which will de-alias them before comparing.
scala> type IS = (Int, String)
defined type alias IS
scala> typeTag[IS] == typeOf[(Int, String)]
res18: Boolean = false
scala> type IS = (Int, String)
defined type alias IS
scala> typeTag[IS] == typeTag[(Int, String)]
res19: Boolean = false
scala> typeOf[IS] == typeOf[(Int, String)]
res20: Boolean = false
scala> typeOf[IS] =:= typeOf[(Int, String)]
res21: Boolean = true // only =:= produces what one would expect on comparing types
I'm confused as how isInstanceOf works in Scala. If I do something like this:
val x: Int = 5
x.isInstanceOf[Int]
Given that Scala does type erasure, shouldn't the JVM remove all type information during runtime?
It's not all type information, just information about generic types. Consider this:
scala> val l = List("foo")
l: List[String] = List(foo)
scala> l.isInstanceOf[List[String]]
res0: Boolean = true
scala> l.isInstanceOf[List[Int]]
<console>:9: warning: fruitless type test: a value of type List[String] cannot also be a List[Int] (the underlying of List[Int]) (but still might match its erasure)
l.isInstanceOf[List[Int]]
^
res1: Boolean = true
They both return true, because the erased type is List.
I'm just learning Scala so I apologize if this has already been discussed but the following seemed a bit odd to me:
scala> import scala.collection.immutable._
import scala.collection.immutable._
scala> val st1 = new WrappedString("Hello")
st1: scala.collection.immutable.WrappedString = Hello
scala> val st2 = new StringOps("Hello")
st2: scala.collection.immutable.StringOps = Hello
scala> st2 == st1
res0: Boolean = true
scala> st1 == st2
res1: Boolean = false
Can anyone explain this? I am using Scala version 2.10.0-M4. I haven't tried this with
anything other versions.
The reason why there occur differences is documented in ScalaDoc.
WrappedString:
The difference between this class and StringOps is that calling
transformer methods such as filter and map will yield an object of
type WrappedString rather than a String.
StringOps:
The difference between this class and WrappedString is that calling
transformer methods such as filter and map will yield a String
object, whereas a WrappedString will remain a WrappedString.
Both derive from collection.GenSeqLike which defines an equals method:
override def equals(that: Any): Boolean = that match {
case that: GenSeq[_] => (that canEqual this) && (this sameElements that)
case _ => false
}
Both implement the canEqual (derived from collection.IterableLike) which returns always true. But StringOps is not a collection.GenIterable:
scala> st1 sameElements st2
<console>:13: error: type mismatch;
found : scala.collection.immutable.StringOps
required: scala.collection.GenIterable[?]
st1 sameElements st2
^
Whereas WrappedString does:
scala> st2 sameElements st1
res13: Boolean = true
So it should obvious why the first case returns true and the other one false.
But why do both exist? I'm not totally sure why it is designed this way, but I think that's because of the fact that a String is not a collection in Scala. When we do some operation on a String like "abc" flatMap (_+"z") we wanna get back another String even though it is not always possible as shown by "abc" map (_+1). This is what StringOps does. But when we have some method def x[A](s: Seq[A]) = s.getClass how shall we call it with a String? In this case we need WrappedString:
scala> x("a")
res9: Class[_ <: Seq[Char]] = class scala.collection.immutable.WrappedString
So, StringOps is more lightweight as WrappedString. It allows us to call some methods on plain old java.lang.String without doing too much overhead. In 2.10 StringOps extend AnyVal. This means it is a value class and its existence can be optimized by scalac (no runtime overhead any more by wrapping the String). In contrast WrappedString allows us to handle a String as a real collection - as an IndexedSeq[Char].