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)
.
The behavior is specified in Pattern Binders and Constructor Patterns.
It looks to me like this:
The expected type of the pattern is (Int, AB)
(in both cases).
In case (id, a @ A(_))
, the expected type of A(_)
is AB
, but the actual type is A
.
In case x @ (_, A(_))
, x
's type is the type of pattern (_, A(_))
. It's instantiated to Tuple2[Int, AB]
before looking inside at A(_)
, and then the A(_)
part is only checked to conform with it: there's nothing in
If the case class is polymorphic, then its type parameters are instantiated so that the instantiation of c conforms to the expected type of the pattern. The instantiated formal parameter types of c's primary constructor are then taken as the expected types of the component patterns p1,…,pn. The pattern matches all objects created from constructor invocations c(v1,…,vn) where each element pattern pi matches the corresponding value vi.
which would do anything with actual types of p1,…,pn.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With