Let us reuse examples from Daily scala :
type PF = PartialFunction[Int,Int]
val pf1 : PF = {case 1 => 2}
val pf2 : PF = {case 2 => 3}
and let us add:
val pf3 : PF = {case 3 => 4}
andThen works as expected here:
pf1 andThen pf2 isDefinedAt(x)
returns true
iff x == 1
(actually, pf2
does not need to be a PartialFunction at all)
However, I expected that:
pf1 andThen pf3 isDefinedAt(x)
would return false
for all x
(i.e., iff pf1 is defined, check for pf3), but it does not and only validates pf1.
In the end, pf1 andThen pf3 lift(x)
always result in a MatchError. I would prefer to get None… I can obtain this behavior by lifting each function such as in pf1.lift(x).flatMap(pf3.lift)
but is there any easier way using pure PartialFunction API? (and without lifting each partial function individually?)
Val functions inherit the andThen function and we will show how to use the andThen function to compose two functions together. Mathematically speaking, (f andThen g)(x) = g(f(x)). The results of the first function f(x) is ran first and will be passed as input to the second function g(x).
When a function is not able to produce a return for every single variable input data given to it then that function is termed as Partial function. It can determine an output for a subset of some practicable inputs only. It can only be applied partially to the stated inputs.
Another example of a partial function is given by y = x + 1 x2 − 3x + 2 , assuming that both the input and output domains are R. This partial function “blows up” for x = 1 and x = 2, its value is “infinity” (= ∞), which is not an element of R. So, the domain of f is R − {1,2}.
If you look at andThen
:
def andThen[C](k: (B) => C): PartialFunction[A, C]
This composes the receiver with a function and not a partial function. That is, k
is expected to be fully defined, it doesn't have isDefinedAt
. Therefore, the resulting partial function does not need to alter the behaviour of isDefinedAt
, it will still just has to consult the first partial function.
You could write your own extension that composes two partial functions:
implicit class ComposePartial[A, B](pf: PartialFunction[A, B]) {
def collect[C](that: PartialFunction[B, C]): PartialFunction[A, C] =
new PartialFunction[A, C] {
def apply(a: A): C = that(pf(a))
def isDefinedAt(a: A) = pf.isDefinedAt(a) && {
val b = pf(a)
that.isDefinedAt(b)
}
}
}
pf1 collect pf2 isDefinedAt(1) // true
pf1 collect pf3 isDefinedAt(1) // false
The problem is that you have to invoke pf(a)
, so given that Scala doesn't enforce purity, you may end up executing side effects unwantedly.
You need the equivalent of flatMap
for PartialFunction
s.
implicit class CollectPartial[A, B](f: PartialFunction[A, B]) {
def collect[C](g: PartialFunction[B, C]) = Function.unlift { a: A =>
f.lift(a).flatMap(g.lift)
}
}
Use it like
val a: PartialFunction[String, Int] = ...
val b: PartialFunction[Int, Char] = ...
val c: PartialFunction[String, Char] = a collect b
This works as expected even with side-effects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With