How does orElse work on PartialFunctions

久未见 提交于 2019-12-18 15:58:25

问题


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

回答1:


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:

  1. the way the PF is defined
  2. the (wrong) assumption that empty is a "catch-all" function that returns Nothing

1. How to define a 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'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.

2. Empty catches nothing!

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") }



回答2:


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.




回答3:


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 PartialFunctions. 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 _ => () }


来源:https://stackoverflow.com/questions/25372561/how-does-orelse-work-on-partialfunctions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!