So here is a puzzle. What will be written by this program:
object Wtf1 {
def main(args: Array[String]): Unit = {
val points: List[Point] = List(
Point(1, 2),
Point(3, 4),
Point(5, 6)
)
val result = points.contains(Point(3, _))
println(s"result: $result")
}
}
case class Point(x: Int, y: Int)
// ANSWER BELOW
// ..............................................
//
The answer is false
.
Why? Because the line with points.contains(Point(3, _))
instead of performing pattern matching, checks whether points
contain a function:
val result = points.contains(Point(3, _))
// in reality is:
val result = names.contains((n: Int) => Point(3, n))
The strangest thing for me is that the compilation of
this code does not generate any warnings.
From Scala compiler point of view the above code is
perfectly valid and this in turn is the result of
List
type being covariant.
Or in the other words because we can assign List[Point]
to
List[Any]
, contains
must accept
arguments of any type:
val points: List[Point] = List(
Point(1, 2),
Point(3, 4),
Point(5, 6)
)
val anys: List[Any] = points
anys.contains(new Object())
The declaration of contains
method in List[A]
looks like this:
def contains[A1 >: A](elem: A1): Boolean
We may snoop the actual types assigned to the generic parameters using a helper method:
def detectType[A, A1 >: A](l: List[A], obj: A1)
(implicit tagA: ClassTag[A], tagA1: ClassTag[A1]): Unit = {
println(s"type of A : ${tagA.runtimeClass.getName}")
println(s"type of A1: ${tagA1.runtimeClass.getName}")
}
detectType(names, Point(3, _))
// This prints:
// type of A : Point
// type of A1: java.lang.Object
//
So during the compilation A1
becomes Object
and everything
type-checks.
Let’s finish by writing a code that actually does what the programmer intended:
val result = names
.collectFirst { case Point(3, _) => true }
.getOrElse(false)