Hongxu Chen
Hongxu Chen

Reputation: 5350

How does PartialFunction deal with undefined values in Scala?

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?

Upvotes: 4

Views: 291

Answers (1)

0__
0__

Reputation: 67280

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.

Upvotes: 6

Related Questions