Why does my pattern match on a collection fail in Scala? - scala

My code is as follows
val hash = new HashMap[String, List[Any]]
hash.put("test", List(1, true, 3))
val result = hash.get("test")
result match {
case List(Int, Boolean, Int) => println("found")
case _ => println("not found")
}
I would expect "found" to be printed but "not found" is printed. I'm trying to match on any List that has three elements of type Int, Boolean, Int

You are checking for a list containing the companion objects Int and Boolean. These are not the same as the classes Int and Boolean.
Use a Typed Pattern instead.
val result: Option[List[Any]] = ...
result match {
case Some(List(_: Int, _: Boolean, _: Int)) => println("found")
case _ => println("not found")
}
Scala Reference, Section 8.1 describes the different patterns you can use.

The first problem is that the get method returns an Option:
scala> val result = hash.get("test")
result: Option[List[Any]] = Some(List(1, true, 3))
So you'd need to match against Some(List(...)), not List(...).
Next, you are checking if the list contains the objects Int, Boolean and Int again, not if it contains objects whose types are Int, Boolean and Int again.
Int and Boolean are both types and object companions. Consider:
scala> val x: Int = 5
x: Int = 5
scala> val x = Int
x: Int.type = object scala.Int
scala> val x: Int = Int
<console>:13: error: type mismatch;
found : Int.type (with underlying type object Int)
required: Int
val x: Int = Int
^
So the correct match statement would be:
case Some(List(_: Int, _: Boolean, _: Int)) => println("found")

The following also works for Scala 2.8
List(1, true, 3) match {
case List(a:Int, b:Boolean, c:Int) => println("found")
case _ => println("not found")
}

Related

Why can Unit be assigned to a Tuple without compilation error?

Check this REPL session, under Scala 2.12.10:
scala> val a = 3 match { case 3 => 1 case 4 => println("why") }
a: AnyVal = 1
scala> val a: Int = 3 match { case 3 => 1 case 4 => println("why") }
<console>:11: error: type mismatch;
found : Unit
required: Int
val a: Int = 3 match { case 3 => 1 case 4 => println("why") }
scala> val (a, b) = 3 match { case 3 => (1, 2) case 4 => println("why") }
a: Any = 1
b: Any = 2
scala> val (a: Int, b) = 3 match { case 3 => (1, 2) case 4 => println("why") }
a: Int = 1
b: Any = 2
scala> val (a, b) = 4 match { case 3 => (1, 2) case 4 => println("why") }
why
scala.MatchError: () (of class scala.runtime.BoxedUnit)
... 36 elided
I would expect the snippets with tuples to not compile as if Unit is ever returned from the second match, then you always get a runtime error. Why does Unit match successfully to Tuple2 in the compiler view?
Unit cannot be assigned to a Tuple
val t: (Int, Int) = () // Error: type mismatch; found: Unit required: (Int, Int)
However the following syntax represents proper pattern matching
val (a: Int, b: Int) = ...
which in your case desugars to something like so
val x: Any = 3 match {
case 3 => (1, 2)
case 4 => println("why")
}
val a: Int = x match {
case t: (_, _) if t._1.isInstanceOf[Int] => t._1.asInstanceOf[Int]
}
val b: Int = x match {
case t: (_, _) if t._2.isInstanceOf[Int] => t._2.asInstanceOf[Int]
}
Notice those asInstanceOf[Int] which convince compiler static type of a and b is Int, however what would happen at runtime is a different story. For example, consider
val t: (Int, Int) = println("why").asInstanceOf[(Int, Int)]
which compiles but fails a runtime.
Analysing -Xprint:jvm output of
lazy val (a: Int, b: Int) = (): Any
we have approximately
val t: Tuple2 = {
val x1: Any = ()
if (x1.isInstanceOf[Tuple2]) {
val x2: Tuple2 = x1.asInstanceOf[Tuple2]
val a: Any = x2._1
val b: Any = x2._2
if (a.isInstanceOf[Int]) {
val x3: Int = scala.Int.unbox(a) // in effect asInstanceOf[Int]
if (b.isInstanceOf[Int]) {
val x4: Int = scala.Int.unbox(b) // in effect asInstanceOf[Int]
new Tuple2(x3, x4)
} else throw new MatchError(x1)
} else throw new MatchError(x1)
} else throw new MatchError(x1)
}
def a: Int = t._1
def b: Int = t._2
whilst
lazy val (a: Int, b: Int) = ()
does not compile, hence if expression on the right of =, in pattern value definition, types to Any it makes all the difference.
Since your partial function can return either a Tuple2[Int, Int] or a Unit, the compiler considers its return type to be the "smallest common supertype" of those two types, that is Any :
scala> val x = 4 match { case 3 => (1, 2) case 4 => println("why") }
why
x: Any = ()
Notice how the return value is x: Any = () and not x: Unit = ().
What you're doing when extracting the tuple is equivalent to this, which compiles (since Any is a supertype of Tuple2), but produces a MatchError:
scala> val (a, b) = ().asInstanceOf[Any]
scala.MatchError: () (of class scala.runtime.BoxedUnit)
... 28 elided

How does find function in Map work in Scala?

I am new to Scala. This is the code that I have written.
object Main extends App {
val mp: Map[String, String] = Map[String, String]("a"->"a", "b"->"b", "c"->"c", "d"->"d")
val s: Option[(String, String)] = mp.find((a: String, b: String) => {
if(a == "c" && b == "c") {
true
}
else {
false
}
})
println(s)
}
I am getting the following error.
error: type mismatch;
found : (String, String) => Boolean
required: ((String, String)) => Boolean
What am I doing wrong?
You need to change
mp.find((a: String, b: String) =>
to either
mp.find(((a: String, b: String)) =>
or
mp.find( case (a: String, b: String) =>
What you have coded is a function expecting two parameters, but you will only be passing in one, which is a Pair (also called Tuple2). The extra braces and the case keyword are ways of specifying that you are only passing in the one parameter, which is an instance of a Pair.
The problem is that find expects a function that takes a single argument, a Tuple2 in this case and returns a Boolean: ((String, String)) => Boolean. However, what you have there is a function that takes two args a and b, not a tuple (brackets matter): (String, String) => Boolean.
Here is one way to fix it. In this case I use pattern matching to extract arguments:
object Main extends App {
val mp: Map[String, String] = Map[String, String]("a"->"a", "b"->"b", "c"->"c", "d"->"d")
val s: Option[(String, String)] = mp.find{ case(a, b) => a == "c" && b == "c" }
println(s)
}
alternatively you could also do:
val s: Option[(String, String)] = mp.find(t => t._1 == "c" && t._2 == "c")
Either would print:
Some((c,c))

Why doesn't pattern match on type fall through failing cases?

See (more details, more details, more details):
scala> val v = Some(9).map { case lst: List[_] => lst; case i: Int => List() }
<console>:7: error: scrutinee is incompatible with pattern type;
found : List[_]
required: Int
val v = Some(9).map { case lst: List[_] => lst; case i: Int => List() }
Since you are using Some(9), the compiler knows that the element being mapped is an Int. A List[_] can never be an Int, so the compiler is telling you that you are doing something that doesn't make sense.
If you want the compiler to treat it as a Some[Any], you'll have to be explicit about the type:
val v = Some(9: Any).map { case lst: List[_] => lst; case i: Int => List() }
// v: Option[List[Any]] = Some(List())
or, more likely:
val x: Option[Any] = Some(9)
val v = x.map { case lst: List[_] => lst; case i: Int => List() }
But, for the record, you are probably doing something you shouldn't be and you should rethink your code.

Function type definition and type erasure in Scala

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

HowTo get the class of _ :Any

I've wrapped a Message and would like to log which message I've wrapped.
val any :Any = msg.wrappedMsg
var result :Class[_] = null
The only solution I could find is matching everything:
result = any match {
case x:AnyRef => x.getClass
case _:Double => classOf[Double]
case _:Float => classOf[Float]
case _:Long => classOf[Long]
case _:Int => classOf[Int]
case _:Short => classOf[Short]
case _:Byte => classOf[Byte]
case _:Unit => classOf[Unit]
case _:Boolean=> classOf[Boolean]
case _:Char => classOf[Char]
}
I wonder if there's a better solution?
The following 2 approaches do not work :(
result = any.getClass //error
// type mismatch; found : Any required: ?{val getClass: ?}
// Note: Any is not implicitly converted to AnyRef.
// You can safely pattern match x: AnyRef or cast x.asInstanceOf[AnyRef] to do so.
result = any match {
case x:AnyRef => x.getClass
case x:AnyVal => /*voodoo to get class*/ null // error
}
//type AnyVal cannot be used in a type pattern or isInstanceOf
You can safely call .asInstanceOf[AnyRef] on any Scala value, which will box primitives:
scala> val as = Seq("a", 1, 1.5, (), false)
as: Seq[Any] = List(, 1, 1.5, (), false)
scala> as map (_.asInstanceOf[AnyRef])
res4: Seq[AnyRef] = List(a, 1, 1.5, (), false)
From there, you can call getClass.
scala> as map (_.asInstanceOf[AnyRef].getClass)
res5: Seq[java.lang.Class[_]] = List(class java.lang.String, class java.lang.Int
eger, class java.lang.Double, class scala.runtime.BoxedUnit, class java.lang.Boo
lean)
Tested with 2.8.0.RC6, I don't know it this worked in 2.7.7.
Definitely new in 2.8 are the companion objects for the classes derived from AnyVal. They contain handy box and unbox methods:
scala> Int.box(1)
res6: java.lang.Integer = 1
scala> Int.unbox(res6)
res7: Int = 1
Won't casting just do the trick, as suggested in the error message?
scala> val d:Double = 0.0
d: Double = 0.0
scala> d.asInstanceOf[AnyRef].getClass
res0: java.lang.Class[_] = class java.lang.Double
As of scala 2.10.0, getClass is available on Any (and not just on AnyRef), so you don't need to do any contorsion anymore and can just do any.getClass.
Note though that you still must be prepared to handlet the dual relation between primitive types and their boxed version.
By example getClass on an integer value will return either java.lang.Integer.TYPE (the class of the primitive Int type) or classOf[java.lang.Integer] depending on the static type of the value:
scala> 123.getClass
res1: Class[Int] = int
scala> val x : Int = 123
x: Int = 123
scala> x.getClass
res2: Class[Int] = int
scala> val x: AnyVal = 123
x: AnyVal = 123
scala> x.getClass
res3: Class[_] = class java.lang.Integer
scala> val x: Any = 123
x: Any = 123
scala> x.getClass
res4: Class[_] = class java.lang.Integer