3

I don't think this code should work, but it does (in Scala 2.10):

scala>     ((i: Int) => i.toString match {
     |        case s if s.length == 2 => "A two digit number"
     |        case s if s.length == 3 => "A three digit number"
     |     }): PartialFunction[Int,String]
res0: PartialFunction[Int,String] = <function1>

// other interactions omitted

scala> res0.orElse(PartialFunction((i: Int) => i.toString))
res5: PartialFunction[Int,String] = <function1>

scala> res5(1)
res6: String = 1

How does it work? I would expect a MatchError to be thrown inside res0.

The Scala language specification does not seem to explicitly document how res0 should be interpreted.

3 Answers 3

3

The trick is that the compiler is not interpreting your definition as a total function converted to a partial function -- it's actually creating a partial function in the first place. You can verify by noting that res0.isDefinedAt(1) == false.

If you actually convert a total function to a partial function, you will get the behavior you expected:

scala> PartialFunction((i: Int) => i.toString match {
     |       case s if s.length == 2 => "A two digit number"
     |       case s if s.length == 3 => "A three digit number"
     |     })
res0: PartialFunction[Int,String] = <function1>

scala> res0 orElse ({ case i => i.toString }: PartialFunction[Int, String])
res1: PartialFunction[Int,String] = <function1>

scala> res1(1)
scala.MatchError: 1 (of class java.lang.String)
// ...

In this example, PartialFunction.apply treats its argument as a total function, so any information about where it's defined is lost.

Sign up to request clarification or add additional context in comments.

Comments

2

orElse is defined on PartialFunction so that the argument is treated as a fallback for the cases when the original is not defined. See the API.

2 Comments

This doesn't explain why res0 doesn't throw a MatchError.
It sort of does, really, if you read the documentation linked by @RobinGreen: Composes this partial function with a fallback partial function which gets applied where this partial function is not defined. orElse returns a new partial function which is defined for any value for which either res0 or res1 is defined.
1

You say that if res0 does not match, you want to try your other pf instead. How this essentially works is:

if (res0.isDefinedAt(1)) {
  res0(1)
} else {
  other(1)
}

The orElse call creates an instance of OrElse, which inherits from PartialFunction: https://github.com/scala/scala/blob/master/src/library/scala/PartialFunction.scala#L159

When you now call apply on this OrElse, it will call f1.applyOrElse(x, f2): https://github.com/scala/scala/blob/master/src/library/scala/PartialFunction.scala#L162

This will call if (isDefinedAt(x)) apply(x) else f2(x): https://github.com/scala/scala/blob/master/src/library/scala/PartialFunction.scala#L117-L118

Therefore you will only get a MatchError, when neither of the pf's matches.

5 Comments

I assumed that my attempt to produce a partial function from a total function in res0 would result in an isDefinedAt that always returns true.
Exactly, but why do you expect a MatchError then?
Because it's actually a total function with a match inside it, masquerading as a partial function, or so I thought.
But if you have a PartialFunction[Int, String] it already says "I only accept Ints". If you now also don't have a guard, where should it not match?
If you would only call res0, then you would get a MatchError, but with the fallback to your total function, it will succeed. I don't understand why you would expect anything else.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.