Reputation: 16723
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)
Upvotes: 1
Views: 772
Reputation: 51271
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 callingapply
, because ifisDefinedAt
is false, it is not guaranteedapply
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
.
Upvotes: 1