How orElse works on PartialFunctions

I get very strange behavior (at least it seems to me) using the orElse method defined on PartialFunction

I think 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 that’s not how it behaves, and I have a lot of problems understanding why, since the types of signatures seem to indicate what I showed above.

Here is a transcript of what I observe 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 val b , which does not extend the PartialFunction type.

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 
+8
scala functional-programming read-eval-print-loop partialfunction
source share
3 answers

There are several things in your attempt, but first, consider 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 are two main errors in your code:

  • method for determining PF
  • (incorrect) assumption that empty is a catch-all function that returns Nothing

1. How to determine PartialFunction

 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 will see that it defines a partial function for any x and applies the given function f to it. Now your { case "hello" => println("bye") } is an argument of f , so you will end up with the following (obviously unexpected) PartialFunction :

 val wrong: PartialFunction[String, Unit] = { case x => x match { case "hello" => println("bye") } 

So, when you ask if it will be defined, it will always return true, since it is defined for the 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 do not agree with the coincidence inside .

Since orElse decides whether to call an "else" depending on the result of isDefined , then it is obvious why it fails.

2. Empty does not catch anything!

Straight from docs :

def empty[A, B]: PartialFunction[A, B]

Partial function with empty area. Any attempt to call an empty partial function scala.MatchError exception.

PartialFunction (well, this is not completely partial) that you are looking for:

 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") } 
+26
source share

PartialFunction.empty[A,B] equivalent to:

 { case x: Nothing => x } 

(These are 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 name suggests, it an *empty* block } 

It can't match anything.

.orElse can be understood simply by concatenating 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:

 { case "hello" => println("Bye") } 

or

 { case "hello" => println("Bye"); case x:Nothing => x } 

MatchError obvious.

Note that the documetation document also mentions that empty always throws a MatchError .


From what I can guess, you wanted PartialFunction always match. There is not a single named method in the standard library, but why should it be. You can just write

 { case _ => () } 
+1
source share

The method of applying the PartialFunction object is PartialFunction , which is defined as follows:

 def apply[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) } 

Basically, it takes the form of function A to B and automatically transfers it to the case statement, the problem is that you also transfer the case, and I'm not 100% sure what happens then, you can try passing the function to the application or 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 can also expand the attribute and implement apply and isDefined , as shown here .

+1
source share

All Articles