Reputation: 507
I just got puzzled by the fact that this partial function pf
doesn't blow up with a MatchError
when inner is not InnerA
sealed trait Inner
case class InnerA(name: String) extends Inner
case class InnerB(name: String, value: Int) extends Inner
case class Input(id: String, inner: Inner)
case class Output(id: String, inner: InnerA)
val pf: PartialFunction[Input, Output] = { input =>
input.inner match {
case innerA: InnerA =>
val Input(id, _) = input
Output(id, innerA)
}
}
instead it is simply undefined so this passes
Seq(
Input("1", InnerA("a1")),
Input("2", InnerB("b2", 2)),
Input("3", InnerA("a3"))
).collect(pf) shouldBe Seq(
Output("1", InnerA("a1")),
Output("3", InnerA("a3"))
)
If I add a line I get a compilation warning and trying to pass an InnerB in the collect above throws a MatchError
(as I originally expected):
val pf: PartialFunction[Input, Output] = { input =>
println(input)
input.inner match {
case innerA: InnerA =>
val Input(id, _) = input
Output(id, innerA)
}
}
InnerB(b2,2) (of class casa.InnerB)
scala.MatchError: InnerB(b2,2) (of class casa.InnerB)
Why is this? Is this quirk documented somewhere?
(I'm using Scala 2.13.3)
Upvotes: 1
Views: 82
Reputation: 170713
this partial function pf doesn't blow up with a MatchError when inner is not InnerA
If you call it directly with such an argument
pf(Input("a", InnerB("b", 0)))
you do get a MatchError
; but the whole point of PartialFunction
is to provide additional information in isDefinedAt
, and collect
uses it by not calling pf
where isDefinedAt
returns false
.
Is this quirk documented somewhere?
See paragraph 6.23.1 Translation of specification:
When a
PartialFunction
is required, an additional memberisDefinedAt
is synthesized, which simply returnstrue
. However, if the function literal has the shapex => x match { … }
, thenisDefinedAt
is derived from the pattern match in the following way: each case from the match expression evaluates totrue
, and if there is no default case, a default case is added that evaluates tofalse
.
So for your second version isDefinedAt
is always true
; the first one doesn't exactly fit x => x match...
, but apparently it's supported too. Now the usual way to define a PartialFunction
is like Luis Miguel Mejía Suárez's comment says
{ case Input(id, InnerA(name)) => Output(id, InnerA(name)) }
but it simply gets translated into
x => x match { case Input(id, InnerA(name)) => Output(id, InnerA(name)) }
Upvotes: 2
Reputation: 1008
What is happening is that, in the first case, the compiler is "removing" the input
match from and using input.inner
.
When I run scalac -Xprint:typer Test.scala
, the first code turns into:
final override def applyOrElse[A1 <: Input, B1 >: Output](input: A1, default: A1 => B1): B1 = (input.inner: Inner @unchecked) match {
case (innerA @ (_: InnerA)) => {
val id: String = (input: A1 @unchecked) match {
case (id: String, inner: Inner): Input((id @ _), _) => id
};
Output.apply(id, innerA)
}
case (defaultCase$ @ _) => default.apply(input)
};
final def isDefinedAt(input: Input): Boolean = (input.inner: Inner @unchecked) match {
case (innerA @ (_: InnerA)) => true
case (defaultCase$ @ _) => false
}
Which means that your function will behave like a PartialFunction[Inner, Output]
, so the compiler knows that it doesn't need to warn you that your match is not exhaustive.
On the other hand, when you see the results for the method with the print instruction, you get:
final override def applyOrElse[A1 <: Input, B1 >: Output](input: A1, default: A1 => B1): B1 = ((input.asInstanceOf[Input]: Input): Input @unchecked) match {
case (defaultCase$ @ _) => {
scala.Predef.println("xxx");
input.inner match {
case (innerA @ (_: InnerA)) => {
val id: String = (input: A1 @unchecked) match {
case (id: String, inner: Inner): Input((id @ _), _) => id
};
Output.apply(id, innerA)
}
}
}
case (defaultCase$ @ _) => default.apply(input)
};
final def isDefinedAt(input: Input): Boolean = ((input.asInstanceOf[Input]: Input): Input @unchecked) match {
case (defaultCase$ @ _) => true
case (defaultCase$ @ _) => false
}
In this case, you're creating a PartialFunction[Input, Output]
that is defined for all intervals of Input, and this is fine. But when the compiler checks the inner input.inner match
, it warns you that this match - not the first one - is not exhaustive.
Upvotes: 4