Lifting in Scala

Partial functions are widely used in Scala.


Partial functions are widely used in Scala. Just like their mathematical counterpart, a partial function, e.g f: X => Y, is a generalisation of functions, that don’t necessarily map every element in domain X to Y. You won’t get a response from the function, if you pass a value, which the function do not know how to deal with, instead, something exceptional will happen.

You see partial functions frequently, almost everywhere. Take the following example actor:

class HttpServerActor extend Actor {
  def receive = {
      case StartOperation => start()
          case StopNow => stop()
	      case StopGracefulIn(x) => stop(x)
	        }
		}

HttpServerActor reacts on StartOperation, StopNow and StopGraceful i.e the function can only handle these events. But, what happens, if the function can not handle the input i.e the parameter is not within the function’s domain ?

In fact, if an actor receives an event, which can not be handled, surprisingly, nothing is going to happen i.e you will not see an exception thrown, at all. This behaviour is required to keep the actor’s state healthy. But, it is for now out of scope. The actor example is just intended to show you, that you can meet the partial functions almost everywhere.

Let’s consider the following example. We want to calculate absolute values of given numbers. So, we define first a (buggy) partial function, which returns the absolute value of the number provided. However, the function is only defined for negative and positive numbers. So, the function can not handle the zero:

def abs: PartialFunction[Int, Int] = {
  case x:Int if x < 0 => x * (-1)
    case x:Int if x > 0 => x
    }

abs(-1) // gives 1.
abs(-1) // gives 1.
abs(0)  // scala.MatchError: 0 (of class java.lang.Integer)

While the first two abs calls return expected result, the second call will fail throwing a MatchError. The error thrown might be a legit application behaviour in your case – as long as you do not make the application’s behaviour dependent on exception handling, which would beat the referential transparency. On the other hand, if you want to process a list of integers, which may contain some zeros as well, sometimes, you don’t want to break the iteration because of an invalid input and just move on to the next item in the list while ignoring the invalid one, e.g:

val numbers = List(-4, -2, 0, 2, 4, 6)
numbers.map(abs(_))

The input, zero, would break the iteration because of the invalid value within. Sometimes, this behaviour is not really, what we want; in other words, you want to keep the iteration going, while invalid inputs are being ignored. In this instance, you can utilise lifting to transform the source function to a safer one, e.g using Option types:

val numbers = List(-4, -2, 0, 2, 4, 6)
numbers.map(abs.lift(_))

The map call now will produce Option types: Some(4), Some(2), None, Some(2), Some(4), Some(6) and for the value which doesn’t exist abs’ domain, we get a None instead of throwing an exception. What happened? Basically, the lift function converts the function abs : Int => Int to a function with F[Int] => F[Int].

flatMap Redux

If you use lifted function in combination with flatMap:

val numbers = List(-4, -2, 0, 2, 4, 6)
numbers.flatMap(abs.lift(_))

You will even get the numeric results instead of Option types.

In our examples, we didn’t specify any parameterised type for our lift function. Therefore, the function, that we wanted to lift, have been lifted to Option[T] => Option[T]. However, you could define an another type, abs.liftT.

In fact, there is an another method in PartialFunction type, which you can use to find out, whether a variable is defined within the function’s domain, isDefinedAt():

val numbers = List(-4, -2, 0, 2, 4, 6)
numbers.filter(abs.isDefinedAt(_)).map(abs(_))

In the example above, we filter the numbers using isDefinedAt first, then apply the map. The result will be the same as in the flatMap-example.