I am getting very bizarre behavior (at least it seems to me) with the orElse
method defined on PartialFunction
It would seem to me that:
val a = PartialFunction[String, Unit] {
case "hello" => println("Bye")
}
val b: PartialFunction[Any, Unit] = a.orElse(PartialFunction.empty[Any, Unit])
a("hello") // "Bye"
a("bogus") // MatchError
b("bogus") // Nothing
b(true) // Nothing
makes sense but this is not how it is behaving and I am having a lot of trouble understanding why as the types signatures seem to indicate what I exposed above.
Here is a transcript of what I am observing with Scala 2.11.2:
Welcome to Scala version 2.11.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_11).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val a = PartialFunction[String, Unit] {
| case "hello" => println("Bye")
| }
a: PartialFunction[String,Unit] = <function1>
scala> a("hello")
Bye
scala> a("bye")
scala.MatchError: bye (of class java.lang.String)
at $anonfun$1.apply(<console>:7)
at $anonfun$1.apply(<console>:7)
at scala.PartialFunction$$anonfun$apply$1.applyOrElse(PartialFunction.scala:242)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
... 33 elided
scala> val b = a.orElse(PartialFunction.empty[Any, Unit])
b: PartialFunction[String,Unit] = <function1>
scala> b("sdf")
scala.MatchError: sdf (of class java.lang.String)
at $anonfun$1.apply(<console>:7)
at $anonfun$1.apply(<console>:7)
at scala.PartialFunction$$anonfun$apply$1.applyOrElse(PartialFunction.scala:242)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:162)
... 33 elided
Note the return type of val b
which has not widen the type of the PartialFunction.
But this also does not work as expected:
scala> val c = a.orElse(PartialFunction.empty[String, Unit])
c: PartialFunction[String,Unit] = <function1>
scala> c("sdfsdf")
scala.MatchError: sdfsdf (of class java.lang.String)
at $anonfun$1.apply(<console>:7)
at $anonfun$1.apply(<console>:7)
at scala.PartialFunction$$anonfun$apply$1.applyOrElse(PartialFunction.scala:242)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:162)
... 33 elided
There's a few things wrong with your attempt, but first let's see a working implementation:
scala> val a: PartialFunction[String, Unit] = { case "hello" => println("bye") }
a: PartialFunction[String,Unit] = <function1>
scala> val b: PartialFunction[Any, Unit] = { case _ => println("fallback") }
b: PartialFunction[Any,Unit] = <function1>
scala> val c = a.orElse(b)
c: PartialFunction[String,Unit] = <function1>
scala> c("hello")
bye
scala> c("foo")
fallback
There's two main errors in your code:
empty
is a "catch-all" function that returns Nothing
val right: PartialFunction[String, Unit] = {
case "hello" => println("bye")
}
How not to define it:
val wrong = PartialFunction[String, Unit] {
case "hello" => println("bye")
}
If you look at the definition of PartialFunction.apply
def apply[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) }
you'll see that it defines a partial function for any x
and it applies the given f
function to it. Now your { case "hello" => println("bye") }
is the f
argument, so you approximately end up with the following (clearly unexpected) PartialFunction
:
val wrong: PartialFunction[String, Unit] = {
case x => x match {
case "hello" => println("bye")
}
So when you ask whether it's defined it will always return true, since it's defined for any string:
wrong.isDefinedAt("hello") // true (ok)
wrong.isDefinedAt("whatever") // true (sure?)
but when you try to apply
it
wrong("hello") // bye (ok)
wrong("whatever") // MatchError (BOOM!)
you fall short on the inner match.
Since orElse
decides whether to call the "else" depending on the result of isDefined
, then it's obvious why it fails.
Straight from the docs:
def empty[A, B]: PartialFunction[A, B]
The partial function with empty domain. Any attempt to invoke empty partial function leads to throwing
scala.MatchError
exception.
The PartialFunction
(well, it's not really partial anymore) you're looking for is:
val fallback: PartialFunction[Any, Unit] = { case _ => println("fallback") }
or - just to show that we learn from our mistakes -
val fallback = PartialFunction[Any, Unit] { _ => println("fallback") }
You are using the PartialFunction
object apply method which is defined so:
def apply[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) }
Basically it takes a function form A
to B
and automatically wrap it in a case statement, the problem is that you are passing the case too and I'm not 100% sure of what happens then, you can try passing a function to the apply or easily you can try without using the apply method:
scala> val a: PartialFunction[String, Unit] = {
| case "hello" => println("Bye")
| }
a: PartialFunction[String,Unit] = <function1>
scala> val b: PartialFunction[String, Unit] = {
| case _ => println("default")
| }
b: PartialFunction[String,Unit] = <function1>
scala> b("123")
default
You could also extend the trait and implement apply
and isDefined
as shown here.
PartialFunction.empty[A,B]
is equivalent to:
{
case x: Nothing => x
}
(This typechecks, because Nothing
is a subtype of both A
and B
.)
or, equivalently:
{
// note: this is probably not a valid Scala code for a partial function
// but, as PartialFunction.empty's name suggests, it's an *empty* block
}
This cannot match anything.
.orElse
can be understood to simply concatenates lists of case statements from two PartialFunction
s. So, in your case a.orElse(PartialFunction.empty[Any,Unit]
means:
{ case "hello" => println("Bye") } orElse { /* no cases here */ }
which simplifies to:
{ case "hello" => println("Bye") }
or
{ case "hello" => println("Bye"); case x:Nothing => x }
MatchError
is therefore obvious.
Note that the documetation also mentions that empty
always throws MatchError
.
From what I can guess, you wanted a PartialFunction
that always matches. There's no named method in the standard library for that, but why should there be. You can simply write
{ case _ => () }
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