Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining PartialFunctions with andThen in Scala

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?)

like image 920
scand1sk Avatar asked Jan 10 '14 10:01

scand1sk


People also ask

How do you use andThen in Scala?

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).

How to define partial function in Scala?

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.

What is partial function example?

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}.


2 Answers

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.

like image 132
0__ Avatar answered Oct 12 '22 13:10

0__


You need the equivalent of flatMap for PartialFunctions.

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.

like image 38
Paul Draper Avatar answered Oct 12 '22 13:10

Paul Draper