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.
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].
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.
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/)
Can somebody explain why the following does not work. Somehow looses the compile some information for the type inference when i do toSet, but i don't understand why.
scala> case class Foo(id: Int, name: String)
defined class Foo
scala> val ids = List(1,2,3)
ids: List[Int] = List(1, 2, 3)
scala> ids.toSet.map(Foo(_, "bar"))
<console>:11: error: missing parameter type for expanded function ((x$1) => Foo(x$1, "bar"))
ids.toSet.map(Foo(_, "bar"))
^
scala> ids.map(Foo(_, "bar")).toSet
res1: scala.collection.immutable.Set[Foo] = Set(Foo(1,bar), Foo(2,bar), Foo(3,bar))
Suppose I've got the following:
trait Pet {
def name: String
}
case class Dog(name: String) extends Pet
val someDogs: List[Dog] = List(Dog("Fido"), Dog("Rover"), Dog("Sam"))
Set isn't covariant in its type parameter, but List is. This means if I have a List[Dog] I also have a List[Pet], but a Set[Dog] is not a Set[Pet]. For the sake of convenience, Scala allows you to upcast during a conversion from a List (or other collection types) to a Set by providing an explicit type parameter on toSet. When you write val a = ids.toSet; a.map(...), this type parameter is inferred and you're fine. When you write ids.toSet.map(...), on the other hand, it's not inferred, and you're out of luck.
This allows the following to work:
scala> val twoPetSet: Set[Pet] = someDogs.toSet.take(2)
twoPetSet: Set[Pet] = Set(Dog(Fido), Dog(Rover))
While this doesn't:
scala> val allDogSet: Set[Dog] = someDogs.toSet
allDogSet: Set[Dog] = Set(Dog(Fido), Dog(Rover), Dog(Sam))
scala> val twoPetSet: Set[Pet] = allDogSet.take(2)
<console>:14: error: type mismatch;
found : scala.collection.immutable.Set[Dog]
required: Set[Pet]
Note: Dog <: Pet, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Pet`. (SLS 3.2.10)
val twoPetSet: Set[Pet] = allDogSet.take(2)
^
Is this worth the confusion? I don't know. But it kind of makes sense, and it's the decision the Collections API designers made for toSet, so we're stuck with it.
Why does this code raise a type mismatch error in Scala 2.9.2? I expected that getOrElse returns type String but actually it returns java.io.Serializable:
scala> implicit def StringToOption(s:String) = Option(s)
StringToOption: (s: String)Option[String]
scala> "a".getOrElse("")
res0: String = a
scala> var opt:Option[String] = "a".getOrElse("")
<console>:8: error: type mismatch;
found : java.io.Serializable
required: Option[String]
var opt:Option[String] = "a".getOrElse("")
^
This is OK:
scala> implicit def StringToOption(s:String): Option[String] = Option(s)
StringToOption: (s: String)Option[String]
scala> var b:Option[String] = "a".getOrElse("") toString
b: Option[String] = Some(a)
It's an unwanted case of incomplete tree traversal. The signature of getOrElse allows type widening, so when it realizes that String is not Option[String] it first tries to fill in a different type ascription on getOrElse, i.e. Serializable. But now it has "a".getOrElse[Serializable]("") and it's stuck--it doesn't realize, I guess, that the problem was making the type too general before checking for implicits.
Once you realize the problem, there's a fix:
"a".getOrElse[String]("")
Now the typer doesn't wander down the let's-widen path, and finds the implicit.