Hi all, I’ve stumbled upon a strange behavior regarding “nested” partial functions. I have the following code:
package example
object TestPF extends App {
val innerPf: PartialFunction[Any, Option[Unit]] = { case x: String => Some(()) }
val outerPf_1: PartialFunction[Any, Any] = { p =>
innerPf(p) match { case Some(_) => println("Something") }
}
val outerPf_2: PartialFunction[Any, Any] = { p =>
val something = p
// Only here compiler warning: "match may not be exhaustive. It would fail on the following input: None"
innerPf(something) match { case Some(_) => println("Something") }
}
println(s"innerPf.isDefinedAt(10): ${innerPf.isDefinedAt(10)}")
println(s"innerPf.isDefinedAt(\"abc\"): ${innerPf.isDefinedAt("abc")}")
try {
println(s"outerPf_1.isDefinedAt(10): ${outerPf_1.isDefinedAt(10)}")
} catch {
case _: MatchError => println("outerPf_1.isDefinedAt(10): MatchError")
}
try {
println(s"outerPf_1.isDefinedAt(\"abc\"): ${outerPf_1.isDefinedAt("abc")}")
} catch {
case _: MatchError => println("outerPf_1.isDefinedAt(\"abc\"): MatchError")
}
try {
println(s"outerPf_2.isDefinedAt(10): ${outerPf_2.isDefinedAt(10)}")
} catch {
case _: MatchError => println("outerPf_2.isDefinedAt(10): MatchError")
}
try {
println(s"outerPf_2.isDefinedAt(\"abc\"): ${outerPf_2.isDefinedAt("abc")}")
} catch {
case _: MatchError => println("outerPf_2.isDefinedAt(\"abc\"): MatchError")
}
// OUTPUT:
// innerPf.isDefinedAt(10): false
// innerPf.isDefinedAt("abc"): true
// outerPf_1.isDefinedAt(10): MatchError
// outerPf_1.isDefinedAt("abc"): true
// outerPf_2.isDefinedAt(10): true
// outerPf_2.isDefinedAt("abc"): true
}
As you can see I define three partial functions:
innerPf
which is a simple partial function only defined on strings;outerPf_1
which is a partial function that internally callsinnerPf
. Notice that the body is just the call toinnerPf
and a match statement, nothing else;outerPf_2
which is similar toouterPf_1
but has an additional instruction before the call toinnerPf
and the match, i.e.val something = p
If you look at the output, there’s something odd. In particular, outerPf_1
and outerPf_2
behave slightly differently, even if I would expect them to be basically identical:
- of the two, I would say that
outerPf_2
is the “normal” one, since it’s defined on all possible inputs (the fact that is will fail later on anything except strings is expected andisDefinedAt
cannot know). In fact, the compiler even warns about the non exhaustive internal pattern match; outerPf_1
, instead is a bit strange: the compiler gives no warning andisDefinedAt
throws a strangeMatchError
, which I suppose should never be thrown by that function.
I tried that same exact code with scala 2.13.11, 3.3.3 and 3.5.1 and the outcome is exactly the same. Is this expected? Why are outerPf_1
and outerPf_2
behaving differently?