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?

1 Answer 1

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.

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

2 Comments

just noticed that several posts didn't explicitly state this issue and defined their PartitionFunctions' apply without isDefinedAt guard. Anyway, I should have read the docs before asking this question; sorry.
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.

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.