Understanding Erasure with Generic Case Class - scala

For the following case class:
scala> case class Foo[T](name: String) {}
defined class Foo
scala> val foo = Foo[Int]("foo")
foo: Foo[Int] = Foo(foo)
Why will Scala let me, as I think it's doing, match on Foo[Int]? Isn't the Int erased?
scala> foo match {
| case _: Foo[Int] => "foo"
| case _ => "bar"
| }
res2: String = foo
But it shows a compile-time error when including another pattern match case?
scala> foo match {
| case _: Foo[String] => "string"
| case _: Foo[Int] => "int"
| case _ => "other"
| }
<console>:12: warning: non-variable type argument String in type pattern Foo[String] is unchecked since it is eliminated by erasure
case _: Foo[String] => "string"
^
<console>:12: error: pattern type is incompatible with expected type;
found : Foo[String]
required: Foo[Int]
case _: Foo[String] => "string"
^

class SuperFoo;
case class Foo[T](name: String) extends SuperFoo {}
val foo: SuperFoo = Foo[Int]("foo")
foo match {
case _: Foo[String] => "foo"
case _ => "bar"
} //> res0: String = foo + warning
In your case compiler knows exact type of foo.

It is erased. In your case compiler can statically check that foo is Foo[Int] and match expression here has sense only with Foo[Int], Foo[_] or Any (AnyRef, scala.Product, scala.Serializable).
But if you hide foo real class using, for example, base class Any:
val foo: Any = Foo[Int]("foo")
val res = foo match {
case _: Foo[String] => "string"
case _ => "other"
}
println(res) // string
You will get warning:
non-variable type argument String in type pattern Foo[String] is
unchecked since it is eliminated by erasure
and program will print "string".

Related

Pattern matching an object (Any) to a Scala Map

I am using a Java library which returns an object of type Object. Now I want to pattern match and get the appropriate type. I expect it to be a Java Map. So, I tried using this:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.collection.JavaConverters._
val any: Any = new java.util.HashMap[Object, Object]
Option(any).flatMap {
case x: java.util.Map[_, _] => Some(x.asScala.toMap)
case x: Map[_, _] => Some(x)
case _ => None
}
// Exiting paste mode, now interpreting.
<console>:17: error: no type parameters for method flatMap: (f: Any => Option[B])Option[B] exist so that it can be applied to arguments (Any => Option[scala.collection.immutable.Map[_,Any]] forSome { type _ })
--- because ---
argument expression's type is not compatible with formal parameter type;
found : Any => Option[scala.collection.immutable.Map[_,Any]] forSome { type _ }
required: Any => Option[?B]
Option(any).flatMap {
^
<console>:17: error: type mismatch;
found : Any => Option[scala.collection.immutable.Map[_,Any]] forSome { type _ }
required: Any => Option[B]
Option(any).flatMap {
^
Not sure what I am doing wrong here.
The below works. The compiler doesn't have the enough information to derive the type here since we are using existential types for our Maps in the pattern matching. This is because unlike Java Map is not a type in scala but Map[T,U] is
Option(any).flatMap[Any]({
case x: java.util.Map[_, _] => Some(x.asScala.toMap)
case x: Map[_, _] => Some(x)
case _ => None
})
If we dont use existential type as shown below, we would be able to use the flatMap without the explicit type parameter specified
scala> Option(any).flatMap({
| case x: java.util.Map[Int, Int] #unchecked => Some(x.asScala.toMap) // using Int as example to create a fully qualified type of Map
| case x: Map[Int, Int] #unchecked => Some(x) // using Int as example to create a fully qualified type of Map
| case _ => None
| })
res5: Option[scala.collection.immutable.Map[Int,Int]] = Some(Map())

Returning <Case Class>.type with Case Class

Given a case class:
scala> case class Foo(x: Int, y: String)
defined class Foo
I can define a method that returns Either[Foo.type, ...].
scala> def f: Either[Foo.type, Int] = Left(Foo)
f: Either[Foo.type,Int]
When I tried to de-construct Foo, I saw a compile-time error:
scala> f match { case Left(Foo(a, b)) => a }
<console>:14: error: constructor cannot be instantiated to expected type;
found : Foo
required: Foo.type
f match { case Left(Foo(a, b)) => a }
But the following worked:
scala> f match { case Left(foo) => foo }
<console>:14: warning: match may not be exhaustive.
It would fail on the following input: Right(_)
f match { case Left(foo) => foo }
res1: Foo.type = Foo
Given a case class, when is it appropriate to use a <CASE CLASS>.type type?
Well, if you want to deconstruct Foo(a,b) then you need to store a Foo and not Foo.type. The statement you have:
def f: Either[Foo.type, Int] = Left(Foo)
Is basically referencing the companion Object every time, and not an instance of your case class. You probably want something like:
def f: Either[Foo, Int] = Left(Foo(1,"foo"))

Pattern Matching/Checking Arity of Function1

Given the following method:
scala> def f: List[Any] => Any = xs => 1234 // this output does not matter
f: List[Any] => Any
Is it possible to pattern match on List[Any] => Any? I don't see an unapply method on Function1, so I believe the answer is no.
Here's what I'm trying to do:
Example:
def foo(x: Any) = x match {
case ... // to handle the case of List[Any] => Any]?
case ...
}
Perhaps I can figure out the arity of x: Any to differentiate between List[Any] => Any versus everything else (_)?
EDIT:
I hope that I do not have to rely on f.toString == <function1>.
No, you can't match exactly on List[Any] => Any due to type erasure, but you can match on Function1 itself:
def foo(x: Any): String = x match {
case _: Function1[_, _] => "some function1"
case _ => "other"
}
Any other matching, like case _: (List[Any] => Any) => "function from list to any" will act the same as case _: Function1[_, _] => "some function":
scala> def foo(x: Any): String = x match {
| case _: (List[Any] => Any) => "function from list to any"
| case _ => "other"
| }
<console>:8: warning: non-variable type argument List[Any] in type pattern List[Any] => Any is unchecked since it is eliminated by erasure
case _: (List[Any] => Any) => "function from list to any"
^
foo: (x: Any)String
scala> def aaa(l: Any): Any = null //it's `Any => Any` - not `List[Any] => Any`!!
aaa: (l: Any)Any
scala> foo(aaa _)
res11: String = function from list to any
scala> foo(1)
res12: String = other
You can match purely on type, even without an unapply method:
def foo(x: Any): String = x match {
case _: (Any => Any) => "some function1"
case _ => "other"
}
And then you can check:
foo(f) //"some function1"
foo(1) //"other"

How to get around this scala compiler erasure warning?

I am trying to compile this scala code and getting the following compiler warnings.
scala> val props: Map[String, _] =
| x match {
| case t: Tuple2[String, _] => {
| val prop =
| t._2 match {
| case f: Function[_, _] => "hello"
| case s:Some[Function[_, _]] => "world"
| case _ => t._2
| }
| Map(t._1 -> prop)
| }
| case _ => null
| }
<console>:10: warning: non-variable type argument String in type pattern (String, _) is unchecked since it is eliminated by erasure
case t: Tuple2[String, _] => {
^
<console>:14: warning: non-variable type argument _ => _ in type pattern Some[_ => _] is unchecked since it is eliminated by erasure
case s:Some[Function[_, _]] => "world"
^
The answers given on How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections? seems to point to this same issue. But I couldn't infer a solution in this particular context.
Use case t # (_: String, _) instead of case t: Tuple2[String, _] and case s # Some(_: Function[_, _]) instead of case s:Some[Function[_, _]].
Scala cannot match on type parameters.
I would rewrite it like this:
x match {
case (name, method) => {
val prop =
method match {
case f: Function[_, _] => "hello"
case Some(f: Function[_, _]) => "world"
case other => other
}
Map(name -> prop)
}
case _ => null
}

How to test a value on being AnyVal?

Tried this:
scala> 2.isInstanceOf[AnyVal]
<console>:8: error: type AnyVal cannot be used in a type pattern or isInstanceOf test
2.isInstanceOf[AnyVal]
^
and this:
scala> 12312 match {
| case _: AnyVal => true
| case _ => false
| }
<console>:9: error: type AnyVal cannot be used in a type pattern or isInstanceOf test
case _: AnyVal => true
^
The message is very informative. I get that I can't use it, but what should I do?
I assume you want to test if something is a primitive value:
def testAnyVal[T](x: T)(implicit evidence: T <:< AnyVal = null) = evidence != null
println(testAnyVal(1)) // true
println(testAnyVal("Hallo")) // false
println(testAnyVal(true)) // true
println(testAnyVal(Boolean.box(true))) // false
I assume that your type is actually Any or you'd already know whether it was AnyVal or not. Unfortunately, when your type is Any, you have to test all the primitive types separately (I have chosen the variable names here to match the internal JVM designations for the primitive types):
(2: Any) match {
case u: Unit => println("Unit")
case z: Boolean => println("Z")
case b: Byte => println("B")
case c: Char => println("C")
case s: Short => println("S")
case i: Int => println("I")
case j: Long => println("J")
case f: Float => println("F")
case d: Double => println("D")
case l: AnyRef => println("L")
}
This works, prints I, and does not give an incomplete match error.