Saturday, April 21, 2018

Functions and Closures in Scala

In Scala, like Java, most common way of defining a function is as a member of a object. Such  a function is called Method.
object MethodDemo extends App{
  def isGreaterThan2(i: Int) = {
    println("Inside isGreaterThan2")
    i > 2  }
  println(MethodDemo.isGreaterThan2(7))
  println(MethodDemo.isGreaterThan2(1))
}

A very important part of functional programming is to decompose the program into as many small functions as possible. This makes these individual building blocks easy to understand, and enables programmers to combine these functions to do complex things with more flexibility.

First class functions


Scala not only enables defining functions as methods, but also allows to write functions as literals without any name and then pass them around as value objects.
object FirstClassFunctionDemo {
  val increment = (x: Int) => x+1
  def main(args: Array[String]): Unit = {
    println(increment(0))
  }
}
What is happening here is that Scala compiler  compiles literal function into a class extending a Scala trait Function1(as there is only one parameter), and it's 'apply' method is implemented with the body provided. During run-time, this class is instantiated as functional value and object is assigned to function variable 'increment'.
When we do:
increment(0)
It translates to:
increment.apply(0) 
'increment' is a function variable and it has a type(Int => Int), and just like any other variable it can be moved around, and assigned to other variables.
var anotherIncr = increment

Partially Applied Functions

In Scala, when we invoke a function with passed arguments, we say we apply function on the passed arguments.
val product(x: Int,y: Int,z: Int) = x * y * z
product(11,12,13)
Scala also allows to define partially applied functions.
scala> val p = product(1, _: Int, _: Int)
p: (Int, Int) => Int = $$Lambda$1134/97761958@24fe2032
In this code, we define a partially applied function,p. REPL output shows that Scala compiler generates a class extending trait Function2(meaning two input parameters), creates a function value from it and assigns it to p. Generated class's apply method takes 2 integer arguments, and invokes product with these two arguments and 1 as third argument.
scala> p(2,5)
res35: Int = 10

Closures


When we define functions in Scala, we make use of arguments which have a meaning in context of the function.
val multiplyByTwo = (x: Int) => x * 2
Here, x has a meaning in context of  'multiplyByTwo' and x is called a bound variable. Such a function literal is called close term. Scala also allows us to do following:
var factor = 1val multiply = (x: Int) => x * factorprintln(multiply(10))
factor = 2println(multiply(10))
factor doesn't have a binding from point of view of function 'multiply' here. Such a variable is called is free variable, and such a function literal is called open term because it is 'open'  to capture bindings for factor. During run time, function value created for this function literal captures the binding for factor by searching into surrounding scope. If it is not found, error is thrown. Else, the function value created is called closure, as it has now captured binding for factor and closed the open term. Also, if captured variable changes as program runs, closure always comes to know about it. So while output of first println above is 10, output of second println is 20 as change in factor is tracked by the closure. And this change tracking is in both ways. If function modifies captured variable, it is known outside the closure.

Tail recursion

Scala encourages data immutability. So traditional way of implementing some algorithms may be not preferable in Scala. For example, finding out sum of all elements in a list may seem like this:
val sumTraditional = (list: List[Int]) => {
  var sum = 0;
  for(i <- list){
    sum += i
  }
  sum
}
Functional programming discourages this style as here data is getting mutated.
Functional way of doing this would be a recursive algorithm:
def sumRecursive(list: List[Int]): Int = {
  list match {
    case Nil => 0    case x::xs => x + sumRecursive(xs)
  }
}
But recursive calls may fail if list is too big. Scala provides solution to this problem with a solution called tail recursion. Tail recursion means when last thing in the function is the recursive call. Scala compiler detects it and instead of making a new call,it jumps to start of the function after updating function parameters with new values. This compiler optimization results in no new call frame added to stack during run time. So even very big collections can be handled recursively.
@tailrecdef sumTailRecursive(list: List[Int], accumulator: Int): Int = {
  list match {
    case Nil => accumulator
    case x::xs => {
      sumTailRecursive(xs,accumulator + x)
    }
  }
}
Scala provides annotation @tailrec to check that recursive call is tail recursive or not.


No comments:

Post a Comment