Friday, April 6, 2018

Reducing code duplication using high order function

Scala provides various mechanisms for control abstraction. High-order function is one such way.

While in objected oriented languages,like Java, class inheritance is primary mechanism for avoiding code duplication, in functional programming languages,like Scala,writing higher-order functions is the preferred way of achieving same.

Higher-order functions are those functions which take functions as parameters. These functions enable us to create control abstractions which reduce code duplication in a major way.

Let's look into an example. Suppose you are writing a basic program which performs math operations on two input lists. We can start in a way used most commonly in non functional programming languages.

object BasicMathOperationsOnListPair {

  def add2Lists(intsX: List[Int],intsY: List[Int]) = {
    val sum = for ((x, y) <- (intsX zip intsY)) yield x + y
    sum
  }

  def multiply2Lists(intsX: List[Int],intsY: List[Int]) = {
    val mult = for ((x, y) <- (intsX zip intsY)) yield x * y
    mult
  }

  def subtract2Lists(intsX: List[Int],intsY: List[Int]) = {
    val subtract = for ((x, y) <- (intsX zip intsY)) yield x - y
    subtract
  }
}
Here, in every method we iterate through two input lists and apply appropriate math operations on the individual elements of the lists pairwise. But we are duplicating the iteration code in all the methods. Can we avoid this?Is it possible to factor out this repetitive code in a common method?

In functional programming languages higher order functions are every where, and we can make us of it to solve our problem. What we need to do is to write a function which contains iteration code and takes another function as input. This input function is then applied on the list elements pairwise during the looping. As can be seen from the nature of operations we are performing on the list elements, this input function should take two integer inputs and return an integer output. Let's write our higher order function.

def applyOpOnListsPairwise(intsX: List[Int], intsY: List[Int], op:(Int,Int) => Int) = {
  val result = for ((x, y) <- (intsX zip intsY)) yield op(x,y)
  result
}
Our higher order function here takes three parameters. First two are the integer lists to be operated upon pairwise. Third parameter,op, is of interest to us, and it is a function defined with signature as:

(Int,Int) => Int
So, now any function which meets this signature can be supplied as input to our higher order function.Let's now write our other functions to make use of our higher order function.

def add2ListsFPWay(intsX: List[Int],intsY: List[Int]) = {
  applyOpOnListsPairwise(intsX,intsY, (x:Int,y:Int) => x + y)
}

def multiply2ListsFPWay(intsX: List[Int],intsY: List[Int]) = {
  applyOpOnListsPairwise(intsX,intsY, (x:Int,y:Int) => x * y)
}

def subtract2ListsFPWay(intsX: List[Int],intsY: List[Int]) = {
  applyOpOnListsPairwise(intsX,intsY, (x:Int,y:Int) => x - y)
}
As can be seen, now our methods no more contain iteration code, which is now residing nicely and uniquely in 'applyOpOnListsPairwise'. We just structure our input function as per our need, and make sure that it meets the signature required for the input function of 'applyOpOnListsPairwise'.

Another advantage here is that we are making use of val. This brings immutability in our program. Similar program in a non-functional programming language would have made us to use mutable data structures.