Manu Chadha Manu Chadha - 16 days ago 7
Scala Question

isDefinedAt with orElse and andThen scala partial functions

I am trying to understand how orElse and andThen works but the behaviour 'isDefinedAt' is unexpected when used with orElse and andThen. In my implementation, the isDefinedAt returns true but the function crashes. This makes the implementation unreliable. How can I solve the problem?

I have written following 3 partial functions

PF1

// a function which is defined for even values only. Returns the value
scala> val forEvenOnly:PartialFunction[Int,Int] = {case d if ( (d % 2) == 0) => d}
forEvenOnly: PartialFunction[Int,Int] = <function1>


PF2

// a function which is defined for odd values only. Returns the value
//wanted to try an alternate syntax
scala> class forOddOnly extends PartialFunction[Int,Int] {
| def apply(x:Int) = x
| def isDefinedAt(x:Int) = (x % 2 !=0)
| }
defined class forOddOnly


PF3

//a function which prints a value if it is less than 100
scala> val lessThan100:PartialFunction[Int,Unit] = {case d if d<100 =>println(d)}
lessThan100: PartialFunction[Int,Unit] = <function1>


Individually, they seem to be workng fine (well except forOddOnly which should crash for even values but doesn't)

scala> val forEvenOnly:PartialFunction[Int,Int] = {case d if ( (d % 2) == 0) => d}
forEvenOnly: PartialFunction[Int,Int] = <function1>

scala> forEvenOnly(1)
scala.MatchError: 1 (of class java.lang.Integer)

scala> forEvenOnly(2)
res60: Int = 2

scala> forEvenOnly.isDefinedAt(1)
res61: Boolean = false

scala> forEvenOnly.isDefinedAt(2)
res62: Boolean = true


Question 1 - In following code, the function forOddOnly behaves differently. It doesnt crash for even values but I want it to. Do I need to use 'case' to get this behaviour? I anticipate that the problem could be in implementation of forOddOnly because it works for all Int in apply

scala> class forOddOnly extends PartialFunction[Int,Int] {
| def apply(x:Int) = x // I cannot change it to def apply(x:Int) = if (x % 2 != 0 ) x. This will not compile
| def isDefinedAt(x:Int) = (x % 2 !=0)
| }
defined class forOddOnly

scala> (new forOddOnly).isDefinedAt(1)
res64: Boolean = true

scala> (new forOddOnly).isDefinedAt(2)
res65: Boolean = false

scala> (new forOddOnly)(1)
res66: Int = 1


this doesn't throw exception
scala> (new forOddOnly)(2)
res67: Int = 2

printing values less than 100 works fine
scala> val lessThan100:PartialFunction[Int,Unit] = {case d if d<100 =>println(d)}
lessThan100: PartialFunction[Int,Unit] =

scala> lessThan100(1)
1

scala> lessThan100(100)
scala.MatchError: 100 (of class java.lang.Integer)

scala> lessThan100.isDefinedAt(1)
res86: Boolean = true

scala> lessThan100.isDefinedAt(100)
res87: Boolean = false


Question2 - In following code, I use 'andThen' with forEvenOnly function and lessthan100. The isDefinedAt returns true for even values greater than 100 but passing the value of 100 or more crashes the code. This makes calling isDefinedAt unreliables. Why am I getting this and how to solve it? isDefinedAt works fine for all odd values as expected (and code also crashes is odd value is passed). If I change the implementation of forOddOnly and use 'case', it works but can't I get the same behaviour using apply?

scala> val printEvenLessThan100 = forEvenOnly andThen lessThan100
printEvenLessThan100: PartialFunction[Int,Unit] = <function1>

scala> printEvenLessThan100.isDefinedAt(1)
res88: Boolean = false

scala> printEvenLessThan100(1)
scala.MatchError: 1 (of class java.lang.Integer)

scala> printEvenLessThan100.isDefinedAt(2)
res89: Boolean = true

scala> printEvenLessThan100(2)
2


problem code. isDefinedAt returns true for even values greater than 100 but code crashes
scala> printEvenLessThan100.isDefinedAt(102)
res94: Boolean = true

scala> printEvenLessThan100(102)
scala.MatchError: 102 (of class java.lang.Integer)

scala> printEvenLessThan100.isDefinedAt(101)
res91: Boolean = false

scala> lessThan100(101)
scala.MatchError: 101 (of class java.lang.Integer)


Question 3 - using andThen with forOddOnly crashes for values more than 99 but not for even values. I guess the there is some common mistake I am making in all these examples

scala> val printOddLessThan100 = (new forOddOnly) andThen lessThan100
printOddLessThan100: PartialFunction[Int,Unit] = <function1>

scala> printOddLessThan100(1)
1

//does not crash
scala> printOddLessThan100(2)
2

scala> printOddLessThan100.isDefinedAt(100)
res102: Boolean = false

scala> printOddLessThan100(100)
scala.MatchError: 100 (of class java.lang.Integer)

scala> printOddLessThan100.isDefinedAt(101)
res97: Boolean = true

scala> printOddLessThan100(101)
scala.MatchError: 101 (of class java.lang.Integer)


The same happens when I combine all the functions using orElse and andThen

scala> val printIntLessThan100 = forEvenOnly orElse (new forOddOnly) andThen lessThan100
printIntLessThan100: PartialFunction[Int,Unit] = <function1>

scala> printIntLessThan100(1)
1

scala> printIntLessThan100(2)
2

//why does this return true when the code actually crashes
scala> printIntLessThan100.isDefinedAt(101)
res83: Boolean = true

scala> printIntLessThan100(101)
scala.MatchError: 100 (of class java.lang.Integer)

Answer

I think this addresses questions 1 and 3.

From the Standard Library scaladoc (emphasis added):

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.

The main distinction between PartialFunction and scala.Function1 is that the user of a PartialFunction may choose to do something different with input that is declared to be outside its domain.

So a runtime exception is not required when calling a PF with a passed parameter for which it is not defined. If the PF is not defined for that input then the result is simply not defined.

The answer to question 2 is also found on that same page, in the description of andThen():

returns a partial function with the same domain as this partial function, which maps arguments x to k(this(x)).

If you have val pfR = pfA andThen pfB then pfR will have the same domain as pfA.

Comments