I would like to try partial functions with a deep pattern matching use case. This initially (of course) didn't work after applying Some(Some(3)), but seemed defined instead :
def deepTest : PartialFunction [Option[Option[Int]], Int] = {
case Some(v) => v match {
case None => 3
}
case None => 1
}
and I thought that by decoupling the nested pattern matching, things would be easier:
def deepTestLvl1 : PartialFunction [Option[Option[Int]], Option[Int]] = {
case Some(v) => v
case None => Some(1)
}
def deepTestLvl2 : PartialFunction [Option[Int], Int] = {
case None => 3
}
but the result was the following:
scala> (deepTestLvl1 andThen deepTestLvl2) isDefinedAt(Some(Some(3)))
res24: Boolean = true
and after applying:
scala> (deepTestLvl1 andThen deepTestLvl2) (Some(Some(3)))
scala.MatchError: Some(3) (of class scala.Some)
at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:248)
at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:246)
at $anonfun$deepTestLvl2$1.applyOrElse(<console>:7)
at $anonfun$deepTestLvl2$1.applyOrElse(<console>:7)
....
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
Am I doing something incorrectly? Shouldn't the isDefinedAt be called twice when I composed sequentially deepTestLvl{1,2} and give me the correct answer?
Very good question.
Let's check the source and see what's happening under the covers:
override def andThen[C](k: B => C): PartialFunction[A, C] =
new AndThen[A, B, C] (this, k)
We can observe here that andThen
doesn't even expect a Partial Function, any Function that transforms the result will do. Your code works, because: trait PartialFunction[-A, +B] extends (A => B)
. This can actually be found in the documentation:
def andThen[C](k: (B) ⇒ C): PartialFunction[A, C]
Composes this partial function with a transformation function that gets applied to results of this partial function.
C the result type of the transformation function.
k the transformation function
returns a partial function with the same domain as this partial function, which maps arguments
x
tok(this(x))
.
So there's currently no way to chain PartialFunction
s in the way you would like, because as Robin said, it would require applying the function. Next to being computationally expensive it could also have side effects, which is a bigger problem.
Whipped together an implementation you're looking for. Use it cautiously! As I already mentioned, if your code has side effects it will cause problems:
implicit class PartialFunctionExtension[-A, B](pf: PartialFunction[A, B]) {
def andThenPf[C](pf2: PartialFunction[B, C]) = new PfAndThen(pf, pf2)
class PfAndThen[+C](pf: PartialFunction[A, B], nextPf: PartialFunction[B, C]) extends PartialFunction[A, C] {
def isDefinedAt(x: A) = pf.isDefinedAt(x) && nextPf.isDefinedAt(pf.apply(x))
def apply(x: A): C = nextPf(pf(x))
}
}
Trying it out:
deepTestLvl1.andThenPf(deepTestLvl2).isDefinedAt(Some(Some(3))) // false
deepTestLvl1.andThenPf(deepTestLvl2).isDefinedAt(Some(None)) // true
deepTestLvl1.andThenPf(deepTestLvl2).apply(Some(None)) // 3