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.

Robin Green
  • 32,079
  • 16
  • 104
  • 187

3 Answers3

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.

Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
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.

Oleg Mirzov
  • 155
  • 3
  • 8
  • This doesn't explain why `res0` doesn't throw a `MatchError`. – Robin Green Nov 14 '13 at 09:27
  • 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. – Nicolas Rinaudo Nov 14 '13 at 13:18
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.

drexin
  • 24,225
  • 4
  • 67
  • 81
  • 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`. – Robin Green Nov 14 '13 at 09:28
  • Exactly, but why do you expect a `MatchError` then? – drexin Nov 14 '13 at 13:13
  • Because it's actually a total function with a match inside it, masquerading as a partial function, or so I thought. – Robin Green Nov 14 '13 at 14:02
  • 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? – drexin Nov 14 '13 at 14:34
  • 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. – drexin Nov 14 '13 at 14:38