Let's consider following trait:
sealed trait AB
case class A(a: Int) extends AB
case class B(b: Int) extends AB
I am trying to collect
to limit a collection to specific subclass.
If I try to collect
, matching individual components and reassembling the tuple:
scala> Seq.empty[(Int, AB)].collect { case (id, a @ A(_)) => (id, a) } : Seq[(Int, A)]
res6: Seq[(Int, ab.A)] = List()
compiler is happy, but if try to return a full match:
scala> Seq.empty[(Int, AB)].collect { case x @ (_, A(_)) => x } : Seq[(Int, A)]
things get ugly:
<console>:27: error: type mismatch;
found : Seq[(Int, ab.AB)]
required: Seq[(Int, ab.A)]
Seq.empty[(Int, AB)].collect { case x @ (_, A(_)) => x } : Seq[(Int, A)]
Why Scala compiler is unable to handle the second case?
It seems that this is because the pattern matching proceeds in top-down fashion into the subpatterns, without passing any additional information from the subpatterns to the root pattern.
When
x @ (_, A(_))
is matched, all x
knows about the pattern is that it has the expected type (Int, AB)
, and this becomes the inferred type of x
.
But when you attach pattern variables i
and a
to the subpatterns, then more specific information can be extracted and saved in the inferred types of i
and a
. In this particular case with a @ A(_)
, the following paragraph in the spec seems relevant:
A pattern binder
x@p
consists of a pattern variablex
and a patternp
. The type of the variablex
is the static typeT
of the patternp
.
In the case of A(_)
, the static type can be inferred to be A
just by looking at the top-level element of the pattern, without recursing into subpatterns. Therefore, the type of a
is inferred to be A
, the type of id
is inferred to be Int
, hence (id, a)
is inferred to have type (Int, A)
.