After reading this excellent question/answer on type erasure in Scala, I tried this code. The Scala compiler did not output a type erasure warning.
scala> val x: List[Int] = List(1,2,3)
x: List[Int] = List(1, 2, 3)
scala> x match {
| case List(x: Int) => println("a")
| case _ => println("false")
| }
false
Why doesn't the above code output the same warning as this code:
scala> List(1,2,3) match {
| case l: List[String] => println("list of strings")
| case _ => println("ok")
| }
<console>:9: warning: fruitless type test: a value of type List[Int] cannot
also be a List[String] (but still might match its erasure)
case l: List[String] => println("list of strings")
^
list of strings
The first case is not just testing type - it's testing by pattern-match that the list has exactly one integer element.
Related
Consider a flatMap written over some case matching. For example:
list.flatMap( v =>
v match {
case Cond1 => if(something) Some(Int) else None
//..Other conditions yielding Option[Int]
case CondN => if(somethingelse) Seq(Int) else Seq()
})
However this wont compile. If the seq is all of Option[Int] or all of Seq[Int] the flatMap works. But not if the Seq is a mix of Options and Seqs. Why is such a restriction in place? Does this solve a particular ambiguity that I cannot think of as of now.
EDIT1
Adding code snippet from REPL
scala> val a = Seq(Option(1), Seq(2,3))
a: Seq[Equals] = List(Some(1), List(2, 3))
scala> val b = Seq(Seq(1), Seq(2,3))
b: Seq[Seq[Int]] = List(List(1), List(2, 3))
scala> a.flatMap(x=>x)
<console>:9: error: type mismatch;
found : Equals
required: scala.collection.GenTraversableOnce[?]
a.flatMap(x=>x)
^
scala> b.flatMap(x=>x)
res24: Seq[Int] = List(1, 2, 3)
EDIT2
After Filippo's answer I tried the following piece of code in the REPL and it worked.
scala> val options = Seq("opt1", "opt2")
options: Seq[String] = List(opt1, opt2)
scala> options.flatMap( x =>
| x match {
| case "opt1" => Some(1)
| case "opt2" => Seq(2,3)
| case _ => None
| })
res27: Seq[Int] = List(1, 2, 3)
How is the resolution different in each of the scenarios.
More importantly when I map instead of flatMap the result is the same as the Seq a that I had created.
scala> options.map( x =>
| x match {
| case "opt1" => Some(1)
| case "opt2" => Seq(2,3)
| case _ => None
| })
res28: Seq[Equals] = List(Some(1), List(2, 3))
Option is a GenTraversableOnce but scala needs some help here:
val a: Seq[TraversableOnce[Int]] = Seq(Option(1), Seq(2,3))
a.flatMap(x=>x)
res0: Seq[Int] = List(1, 2, 3)
EDIT after additions to the question
I think that if the type of your sequence is the one you are expecting, everything boils down to the function passed to the flatMap. If scala can't figure out that the function is (A) => Traversable[A] when the starting sequence is Seq[A], I think we should make some types explicit.
Now, back to your first sample, I would refactor it as:
list.flatMap {
case Cond1 if something => Seq(Int)
case CondN if somethingelse => Seq(Int)
case _ => Seq()
}
No doubt scala is now able to infer the types correctly.
I start learning Scala and I don't quite understand some behaviors of pattern-matching. Can anyone explain to me why the first case works but the second case doesn't work?
1
def getFirstElement(list: List[Int]) : Int = list match {
case h::tail => h
case _ => -1
}
Scala> getFirstElement(List(1,2,3,4))
res: Int = 1
Scala> 1 :: List(1,2)
res: List[Int] = List(1, 1, 2)
2
def getSumofFirstTwoElement(list: List[Int]): Int = list match {
case List(a: Int, b: Int)++tail => a + b
case _ => -1
}
<console>:11: error: not found: value ++
Scala> List(1,2) ++ List(3,4,5)
res: List[Int] = List(1, 2, 3, 4, 5)
The reason is that there is no unapply method on an object of type ++. The reason that :: works is because behind the scenes it is really:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
override def tail : List[B] = tl
override def isEmpty: Boolean = false
}
Source
This leads to how pattern matching is implemented in Scala. It uses extractors (or here), which are basically objects that contain an unapply method, which is provided by default with case classes.
++ is a method on object of list. I think you want:
def getSumofFirstTwoElement(list: List[Int]): Int = list match {
case a::b::tail => a + b
case _ => -1
}
In case two, ++ isn't used for unapply. You want the extractor :: to decompose the list.
A good explanation here.
scala> def getSumofFirstTwoElement(list: List[Int]): Int = list match {
| case a::b::t => a + b
| case _ => -1
| }
getSumofFirstTwoElement: (list: List[Int])Int
scala> getSumofFirstTwoElement(List(1,2,3,4))
res0: Int = 3
When you pattern match again Lists, you can use Nil to check for empty list. However, if the underlying type is an Iterable, you can still check for Nil, and it will break for empty Sets, etc... See following REPL session:
scala> val l: Iterable[Int] = List()
l: Iterable[Int] = List()
scala> l match {
| case Nil => 1
| case _ => 2
| }
res0: Int = 1
scala> val l: Iterable[Int] = Set()
l: Iterable[Int] = Set()
scala> l match {
| case Nil => 1
| case _ => 2
| }
res2: Int = 2
Question is - how can I prevent this kind of issue? Obviously, if l is a type List, it's no bug. And if l is of type Set, it won't compile. But what if we have a class that has a list, define a function that pattern matches in this way, and then someone changes the class to take a generic iterable instead? Is this Nil vs. _ pattern match a bad idea in general?
One possibility is to use a guard:
scala> val xs: Iterable[Int] = Set()
xs: Iterable[Int] = Set()
scala> xs match { case xs if xs.isEmpty => 1 case _ => 2 }
res0: Int = 1
Another way to do this is to use an if-else-expression (works best if you only have one or two conditions to check):
scala> if (xs.isEmpty) 1 else 2
res1: Int = 1
Convert the scrutinee to a list to eliminate doubt.
l.toList match {
case Nil => 1
case xs => 2
}
Here is another option (pun intended):
scala> val l: Iterable[Int] = List()
l: Iterable[Int] = List()
scala> l.headOption match { case None => 1; case Some(h) => 2 }
res0: Int = 1
This is useful in cases where you pattern match in order to get the head like in popular List() match { case h :: t => ... } but it's not a list, it's an Iterable and :: will fail.
I added this answer because I thought it's quite common to pattern match on a collection to get a head, otherwise you can just check with xs.isEmpty.
I have an heterogeneous List like the following one:
val l = List(1, "One", true)
and I need to filter its objects by extracting only the ones belonging to a given Class. For this purpose I wrote a very simple method like this:
def filterByClass[A](l: List[_], c: Class[A]) =
l filter (_.asInstanceOf[AnyRef].getClass() == c)
Note that I am obliged to add the explicit conversion to AnyRef in order to avoid this compilation problem:
error: type mismatch;
found : _$1 where type _$1
required: ?{val getClass(): ?}
Note that implicit conversions are not applicable because they are ambiguous:
both method any2stringadd in object Predef of type (x: Any)scala.runtime.StringAdd
and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
are possible conversion functions from _$1 to ?{val getClass(): ?}
l filter (_.getClass() == c)
However in this way the invocation of:
filterByClass(l, classOf[String])
returns as expected:
List(One)
but of course the same doesn't work, for example, with Int since they extends Any but not AnyRef, so by invoking:
filterByClass(l, classOf[Int])
the result is just the empty List.
Is there a way to make my filterByClass method working even with Int, Boolean and all the other classes extending Any?
The collect method already does what you want. For example to collect all Ints in a collection you could write
xs collect { case x: Int => x }
This of course only works when you hardcode the type but as primitives are handled differently from reference types it is actually better to do so. You can make your life easier with some type classes:
case class Collect[A](collect: PartialFunction[Any,A])
object Collect {
implicit val collectInt: Collect[Int] = Collect[Int]({case x: Int => x})
// repeat for other primitives
// for types that extend AnyRef
implicit def collectAnyRef[A <: AnyRef](implicit mf: ClassManifest[A]) =
Collect[A]({ case x if mf.erasure.isInstance(x) => x.asInstanceOf[A] })
}
def collectInstance[A : Collect](xs: List[_ >: A]) =
xs.collect(implicitly[Collect[A]].collect)
Then you can use it without even passing a Class[A] instance:
scala> collectInstance[Int](l)
res5: List[Int] = List(1)
scala> collectInstance[String](l)
res6: List[String] = List(One)
Using isInstanceOf:
scala> val l = List(1, "One", 2)
l: List[Any] = List(1, One, 2)
scala> l . filter(_.isInstanceOf[String])
res1: List[Any] = List(One)
scala> l . filter(_.isInstanceOf[Int])
res2: List[Any] = List(1, 2)
edit:
As the OP requested, here's another version that moves the check in a method. I Couldn't find a way to use isInstanceOf and so I changed the implementation to use a ClassManifest:
def filterByClass[A](l: List[_])(implicit mf: ClassManifest[A]) =
l.filter(mf.erasure.isInstance(_))
Some usage scenarios:
scala> filterByClass[String](l)
res5: List[Any] = List(One)
scala> filterByClass[java.lang.Integer](l)
res6: List[Any] = List(1, 2)
scala> filterByClass[Int](l)
res7: List[Any] = List()
As can be seen above, this solution doesn't work with Scala's Int type.
The class of an element in a List[Any] is never classOf[Int], so this is behaving as expected. Your assumptions apparently leave this unexpected, but it's hard to give you a better way because the right way is "don't do that."
What do you think can be said about the classes of the members of a heterogenous list? Maybe this is illustrative. I'm curious how you think java does it better.
scala> def f[T: Manifest](xs: List[T]) = println(manifest[T] + ", " + manifest[T].erasure)
f: [T](xs: List[T])(implicit evidence$1: Manifest[T])Unit
scala> f(List(1))
Int, int
scala> f(List(1, true))
AnyVal, class java.lang.Object
scala> f(List(1, "One", true))
Any, class java.lang.Object
This worked for me. Is this what you want?
scala> val l = List(1, "One", true)
l: List[Any] = List(1, One, true)
scala> l filter { case x: String => true; case _ => false }
res0: List[Any] = List(One)
scala> l filter { case x: Int => true; case _ => false }
res1: List[Any] = List(1)
scala> l filter { case x: Boolean => true; case _ => false }
res2: List[Any] = List(true)
Despite my solution could be less elegant than this one I find mine quicker and easier. I just defined a method like this:
private def normalizeClass(c: Class[_]): Class[_] =
if (classOf[AnyRef].isAssignableFrom((c))) c
else if (c == classOf[Int]) classOf[java.lang.Integer]
// Add all other primitive types
else classOf[java.lang.Boolean]
So by using it in my former filterByClass method as it follows:
def filterByClass[A](l: List[_], c: Class[A]) =
l filter (normalizeClass(c).isInstance(_))
the invocation of:
filterByClass(List(1, "One", false), classOf[Int])
just returns
List(1)
as expected.
At the end, this problem reduces to find a map between a primitive and the corresponding boxed type.
Maybe a help can arrive from scala.reflect.Invocation (not included in the final version of 2.8.0), the getAnyValClass function in particular (here slightly edited)
def getAnyValClass(x: Any): java.lang.Class[_] = x match {
case _: Byte => classOf[Byte]
case _: Short => classOf[Short]
case _: Int => classOf[Int]
case _: Long => classOf[Long]
case _: Float => classOf[Float]
case _: Double => classOf[Double]
case _: Char => classOf[Char]
case _: Boolean => classOf[Boolean]
case _: Unit => classOf[Unit]
case x#_ => x.asInstanceOf[AnyRef].getClass
}
With this function the filter is as easy as
def filterByClass[T: Manifest](l:List[Any]) = {
l filter (getAnyValClass(_) == manifest[T].erasure)
}
and the invocation is:
filterByClass[Int](List(1,"one",true))
Given the following type and instance:
type operation = (Int, Int) => Int
def add: operation = _ + _
If I try to match an operation in a case statement, Scala complains about unchecked typing due to type erasure:
for (a <- elements) a match {
case o: operation => // do stuff
}
Is there a way to achieve this kind of function-based typing while being erasure-friendly in case statements?
Note, this is similar to this thread.
One easy way to deal with type erasure is to create an unparamaterized class. It's not perfect, but it works. Make it a case class that extends Function2 and it's not even too clunky to use either directly or in a pattern match
scala> case class Operation(f : (Int,Int) => Int) extends ((Int,Int) => Int) {
| def apply(x : Int, y : Int) = f(x,y)
| }
defined class Operation
scala> def add = Operation(_ + _)
add: Operation
scala> val y = add(7,3)
y: Int = 10
scala> val elements = List(1, add, 2)
elements: List[Any] = List(1, <function2>, 2)
scala> for (a <- elements) yield a match {
| case Operation(f) => f(1,2)
| case x : Int => x
| }
res0: List[Int] = List(1, 3, 2)
The limitation is that you have to have "boxed" the operation before you lose its type, not after. Also, you end up with one class per concrete function type.
Another, arguably much better, solution is to not lose the type information. Use an Either to retain the static type info.
scala> val elements : List[Either[Int, (Int, Int) => Int]] = List(Left(1), Right(_ + _), Left(2))
elements: List[Either[Int,(Int, Int) => Int]] = List(Left(1), Right(<function2>), Left(2))
scala> for (a <- elements) yield a match {
| case Right(f) => f(1,2)
| case Left(x) => x
| }
res1: List[Int] = List(1, 3, 2)
The limitation here is that it gets clunky if your List can have more than 2 types. But it effectively avoids forcing Scala to be a dynamically typed language, unlike the previous solution.
If you can wrap a into an Option, then this will work:
scala> val a:Option[Any] = Some(add)
a: Option[Any] = Some(<function2>)
scala> a match { case o:Some[operation] => println ("found"); case _ => }
found