4

I defined 2 versions of PartialFunction in Scala.

  val doubleEvens1: PartialFunction[Int, Int] = {
    case x if x % 2 == 0 => x * 2
  }

  val doubleEvens2 = new PartialFunction[Int, Int] {
    override def isDefinedAt(x: Int): Boolean = x % 2 == 0

    override def apply(v1: Int): Int = v1 * 2
  }

and a list:

val list = List(1, 2, 3, 4, 5)

However their behaviors diff for undefined values:

// doubleEvens1
println(doubleEvens1.isDefinedAt(3)) // false
println(list.collect(doubleEvens1))  // List(4, 8)
println(doubleEvens1(3))             // scala.MatchError
println(list.map(doubleEvens1))      // scala.MatchError

// doubleEvens2
println(doubleEvens2.isDefinedAt(3)) // false
println(list.collect(doubleEvens2))  // List(4, 8)
println(doubleEvens2(3))             // 6
println(list.map(doubleEvens2))      // List(2, 4, 6, 8, 10)

I guess doubleEvens1 should be reasonable since it accords with the mathematical definition. But is the behavior of doubleEvens2 designed by purpose? Or am I missing something in the code snippet?

Hongxu Chen
  • 5,240
  • 2
  • 45
  • 85

1 Answers1

6

If you implement your own apply method and do not check against isDefinedAt, then obviously you can call that method with any input without an exception being thrown. This is explicitly stated in the docs:

It is the responsibility of the caller to call isDefinedAt before calling apply, because if isDefinedAt is false, it is not guaranteed apply will throw an exception to indicate an error condition. If an exception is not thrown, evaluation may result in an arbitrary value.

It just so happens that the partial function created from a pattern match will call isDefinedAt itself from its apply method implementation and throw an exception.

So if you want to copy that behaviour, you have to do this:

val doubleEvens2 = new PartialFunction[Int, Int] {
  override def isDefinedAt(x: Int): Boolean = x % 2 == 0

  override def apply(x: Int): Int = 
    if (isDefinedAt(x)) x * 2 else throw new MatchError(x.toString)
}

The drawback of course is that some code (isDefined) might be invoked multiple times, as discussed in this question.

0__
  • 66,707
  • 21
  • 171
  • 266
  • 1
    just noticed that several posts didn't explicitly state this issue and defined their `PartitionFunction`s' `apply` without `isDefinedAt` guard. Anyway, I should have read the docs before asking this question; sorry. – Hongxu Chen Dec 24 '15 at 09:36
  • 2
    The docs say you _may_ define `apply` without `isDefinedAt`. Simply, if the use site does not check `isDefinedAt`, the behaviour of `apply` is undefined. Using collection methods such as `collect` that take a partial function will check `isDefined` (as each second line of your example calls show). Because `PartialFunction` is a sub-type of `Function`, you can run into weird cases such as the `List.map` that doesn't know you are using a partial function and therefore doesn't call `isDefinedAt`. – 0__ Dec 24 '15 at 09:42