Functional programming in Java

Functional Programming

April 14, 2020

This blog is written to make the readers understand the concepts of functional programming thoroughly.
In the previous blogs we have already discussed Lambda and functional interfaces and their various features. Now we will learn how to write programs using the various features of Functional programming.

In general terms, Functional Programming is a programming paradigm, which is programming with functions. It does not replace the other programming paradigms like Procedural or Object Oriented, rather learning it adds another platform to the programmers.

As they say

“Best Architectures and Prgrams are written and maintained when programmers uses the avaliable paradign wisely or use blend of these models like OOPs and Functional programming together.”

In this blog we will discuss the concepts of functional programming i.e. Functions as first class citizens, Pure Functions, Higher Order Functions and referential transparency. Then we will apply these concepts and implement the features of Functional Programming like Functional Chaining, Composition, Closures, Currying, Lazy evaluation and Tail Call optimization.

Following chart gives the summary of the contents which we will be covering in this blog

Functional Programming at a Glance

Functional programming is a programming paradigm in which each and everything in form of pure mathematical functions.

The imperative programming or Object Oriented Programming deals with the object state. That is we can pass the object to a method and we can get the object from the method with modified state. So here the outer environment is generally modified.

But, when dealing with pure mathematical functions we must know that a pure mathematical function’s task would only be to perform calculation on the basis of provided algorithm. It should not have any effect on outer environment. This means that it does not have anything to do with object state. It just uses the provided data for calculation and perform the calculation.

Thus Functional Programming avoids changing-state and mutable data. It is a Declarative type of programming style that focuses on what to solve rather than how to solve Instead of writing statements one after other, functional programming makes use of expressions (which itself produces a value and can be written wherever a value is expected).

So basically this programming paradigm is based on Lambda calculus. Lambda expressions added functional programming to Java from 8th version. But this does not necessarily mean that JAVA is a pure functional programming paradigm. The traditional imperative model will always be available

In Object Oriented Programming we can pass objects to function, we can return objects from functions and we can create objects in functions as well. Similarly, with Functional programming we can do the same with functions. We can pass functions to functions, we can create functions within functions and we can return functions from functions.

Key Concepts of Functional Programming

Functional programming contains the following key concepts:

  • Functions as first class objects
  • Pure functions
  • Higher order functions
  • No Side effects
  • Referencial Transparency

Let us discuss these concepts in detail now

Functions as first class objects

In functional programming, Functions as First class citizens.

In Object oriented programming, we can do anything with variables or data. We can pass them to any function, get result into them from any function. Mostly in java all the time we play with objects and variables Thus we can say that they are treated first class..

Now with the introduction of functional programming in java, functions have also become first class citizens.

A language that considers procedures/functions to be “first-class” allows functions to be passed around just like any other value. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures. Also we can pass them to any function. and we can get result into them from any function..

Pure Functions

There are 2 Characteristics that decides whether a function is pure or not. These are:

  • The output of a pure function depends only on (a) its input parameters and (b) its internal algorithm. Hence, if you call the pure functions with the same set of arguments, you will always get the same return values.
  • A pure function has no side effects, which means that it does not read anything from the outside world or write anything to the outside world. It does not read from a file, web service, UI, or database, and does not write anything either.

Side Effect

So a Side effect can be anything a method does besides computing and returning a value. Any change of instance or class field values is a side effect, as is drawing something on the screen, writing to a file or a network connection.

So, pure functions always return the same result, given the same input. They do not modify the arguments which are passed to them.

Let us consider an example.

public class ObjectWithPureFunction{

    public int sum(int a, int b) {
        return a + b;
    }
}

It can be noticed that the return value of the sum() function only depends on the input parameters. Also the sum() has no side effects, i.e it does not modify any state (variables) outside the function anywhere.

Contrarily, here is an example of a non-pure function:

public class ObjectWithNonPureFunction{
    private int value = 0;

    public int add(int nextValue) {
        this.value += nextValue;
        return this.value;
    }
}

Here, it can be noticed that the method add() uses a member variable to calculate its return value, and it also modifies the state of the value member variable, so it has a side effect.

Let us consider one more example:

public class Demo {
          public static void main(String[] args) {
             int result = add(multiply(2, 3), multiply(3, 4));
            System.out.println(result);
        }

         public static int add(int a, int b) {
             return a + b;
        }
        public static int multiply(int a, int b) {
             log(String.format("Returning %s as the result of %s * %s", a * b, a, b));
            return a * b;
       } 
       public static void log(String m) {
         System.out.println(m);
     }
}

The output of this program is

Returning 6 as the of 2*3
Returning 12 as the result of 3*4
18

Now, if we replace multiply(2, 3) and multiply(3, 4) with their respective return values, then it doesn’t change the signification of the program:

 int x = add(6, 12); 

In contrast, replacing the call to the add function with its return value changes the signification of the program, because the log method will no longer be called.

When a function has no side effects and is deterministic — we call it a “pure” function. We have methods like String uppercase and lowercase methods, List methods like max, min, Math.sin(a), Math.cos(a) etc. which are pure functions in JAVA as they just take input, applies the algorithm and returns the result

Now recall the predefined functional interface Consumer

@FunctionalInterface
Consumer<T>
      void accept(T t);

As we have already discussed in the blog “Understanding Predefined Functional Interfaces”, it does not return anything. It just does some
operation inside it. So this consumer always contains side effects.
This can be called a special case, as there will be no meaning of a consumer if it does not have side effect.

The reason Functional programming strives to work with pure functions because

  • They allow clarity of thought.
  • It is easy to reason about pure functions, because once defined, they are like black boxes whose complexities can be put aside. So they can be used as building blocks to compute operations of ascending complexity.
  • One more big advantage of using pure functions is that pure functions can be used fearlessly in Multithreaded programs as they will never modify the shared State or variables.

Higher Order Functions

Higher-order functions are functions which either take functions as arguments or return them as output (or both).

In Java, we implement Functions using Lambda, so we can say,

A higher order function is a function (method) that takes one or more lambda expressions as parameters, or returns a lambda expression. or possibly does both.

Lets take an example:

Suppose we have to create a factory which produces the items, configures the items and then supplies the items. So we have three interfaces : a IProducer whose job is to produce something, a IConfigurator for configuration and above these two we have IFactory which perform the production and configuration of items. Let us create these interfaces first.

public interface IProducer<T>
{
 T produce();
}
public interface IConfigurator<T>
{
 T configure(T t);
}
public interface IFactory<T>
{
 T create();
}

Now let us create a higher order function in the HigherOrderFunction class

public class HigherOrderFunctionExample {
    public static void main(String[] args) {
   }

    public static <T> IFactory<T> createFactory(IProducer<T> producer, Configurator<T> configurator{
    
        return () -> {
           T product = producer.produce();
           return configurator.configure(product);
        };
    }
}

One example of higher order function from jdk is sort() method of collections class. It sorts the elements present in collection in natural order which is generally ascending order and Collections.sort() method takes a Comparator as parameter. Here is an example:

Let us take a list and sort this using sort method.

public class HigherOrderFunctions {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
		list.add("One");
		list.add("Abc");
		list.add("BCD");

      		Collections.sort(list, (a, b) -> {
		    return b.compareTo(a);    
		});
		
		System.out.println(list);

	}

}

Referencial Transparency

Referential transparency is a property of a function, variable, or expression where the expression can be replaced by its (evaluated) value without affecting the behaviour of the program.

First let us understand referential transparency theoretically

Consider a sentence “Tiger is bigger than a kite” and erase the word “Tiger” to leave a blank. This is called a CONTEXT. By filling the blank in the CONTEXT we get back to a sentence.

“[New Delhi] is bigger than a kite” is true.
“[India’s Capital] is bigger than a kite” is also true.

So, the truth / meaning of the sentence is not affected by the choice of which of these two REFERENCES are used.
Thus “transparent” basically means “doesn’t make a difference”.

In maths, referential transparency is the property of expressions that can be replaced by other expressions having the same value without changing the result in any way.

x = 7 + (2 * 4)

We may replace the subexpression (2 * 4) with any other expression having the same value without changing the result (the value of x) i.e we can use 8 or we can use 2*2*2.

Now let us discuss about referential transparency in context of functional programming. As we know Functional Programming is all about functions, so in functional programming Referential transparency means that a function call can be replaced by its value or another referentially transparent call with the same result.

Consider the following code having two functions, one for add and another for multiply

public class ReferentialTransparency
{
  public static void main (String[] args)
 {  
  int result = add(2, multiply(2, multiply(2,2)));
  System.out.println(result);
 }

  public static int add(int a, int b) {
      return a + b
  }
  public int multiply(int a, int b) {
   return a * b;
  }
}

We may replace the call to the function returning result in the above code by

int result = add(2, multiply(2, 4));

or

int result = add(2, 8);

On the other hand, suppose along with returning the result we print something inside the method:

int add(int a, int b) {
   int result = a + b;
   System.out.println("Returning " + result);
    return result;
}

This time if we replace multiply(2,4) with 8, .we will get the result but the program will not behave as it was intended to do.
However if we notice, the function add is now not a pure function as it is printing something and such implementation is generally avoided in functional programming.
So, It will not print the printing statement.

Now lets consider one more example:

//global G
int val = 10;
int addToVal(int x)
{
return x + G;
}

This can produce different results, for any argument x. The reason for this is that the function uses and modifies a global variable, therefore the result of each invocation depends on this changing state, and not only on the function’s argument which was not intended. ,

This is again not a pure function.
So, if we think little deeply we’ll find that pure functions are always referentially transparent and they do not have any side effects.

The reason functional programming uses Referential transparency is that it makes reasoning about programs easier. It also makes each subprogram independent, which greatly simplifies unit testing and refactoring. As an additional benefit, referentially transparent programs are easier to read and understand.

Standard Practices in Functional Programming

After discussing the key concepts of functional programming , let us now discuss some techniques that are highly used or we can call them standard practices in functional Programming. Following are the techniques.

  • Functional chaining,
  • Functional composition,
  • Closures
  • Currying
  • Lazy Evaluation
  • Tail Call Optimization/recursion

To write better functional code, we need to understand these techniques, so let us discuss each one of these in detail.

The first two techniques Function chaining and Functional composition tells us how to design an API in a Functional way. From JAVA 9, we have default functions and interfaces and APIs take advantage of default methods to make these two techniques possible in java.

Functional Chaining

Moving on, let us take a look at function chaining. As the name suggests, it is a technique to chain functions; each method returns an object allowing the calls to be chained together in a single statement without using any variables to store the intermediate results.

obj.function1().function2().function3().....

Let’s consider an example. The function takes an string and prints it on the console. Lets first create the interface Consumer

@FunctionalInterface
public interface Consumer<T>{
     void accept(T t);
}

Now lets implement it

public class chaining {
            public static void main(String[] args) {
            Consumer<String> c1 = s -> System.out.println(s);
            Consumer<String> c2 = s -> System.out.println(s);
            c1.accept(“Hello”);
           c2.accept(“Hello”);
         Consumer<string> c3 = s ->
        {
            c1.accept(s);
            c2.accept(s);
        };
       c3.accept(“Hello”);
      }
}

Output:

Hello

Hello

Hello will be printed twice.

Thus two operations are performed one after the other but it does not look like the chining operation exactly. Let us consider

Consumer<String> c4 = c1.thenAccept(c2);

The interface for this can be created

public interface Consumer<T> {
            void accept(T t);
            default Consumer<T>  thenAccept(Consumer<T> next) {
                        return (T t) -> {
                                    this.accept(t);
                                    next.accept(t);
                      };
               }
}

Now let us invoke the accept method on c4

Consumer<String> c4 = c1.thenAccept(c2);
c4.accept("BasicsStrong");

This will display “BasicsStrong” twice.

Here we can observe that with the call to accept method by providing
the value we are just triggering the complete chain we developed earlier. But this program is not fail proof !

If we comment the statement c4.accept and pass null as the next consumer to andThen method, then the program is not going to execute the andThen method and we are not going to get the one necessary exception “NullPointerException”, but this should not happen.
So we should go for a null check or something like that to avoid
such a thing.

So we will add a requiredNotNull function to the interface.

public interface Consumer<T> {
            void accept(T t);
            default Consumer<T>  thenAccept(Consumer<T> next) {
                            Objects.requiredNotNull(next);
                        return (T t) -> {
                                    this.accept(t);
                                    next.accept(t);
                      };
               }
}

Thus we can chain the functions. Consider another example

	public class chaining {
            public static void main(String[] args) {
            Consumer<String> c1 = s -> System.out.println(s);
            Consumer<String> c2 = s -> System.out.println(s);
            c1.accept(“Hello”);
           c2.accept(“Hello”);
         Consumer<string> c3 = s ->
        {
            c1.accept(s);
            c2.accept(s);
        };
       c3.accept(“Hello”);
      Function<Integer,Integer> f1= (s)-> s+2;
      Function<Integer,Integer> f2= (s)-> s*2;
      Function<Integer,Integer> f3 = f1.andThen(f2);
			
	Integer result = f3.apply(10);
	System.out.println(result);
}
}

So it will execute first f1 and then f2. So output will by 24

Functional composition

Now take a look at functional composition. Functional composition follows the reverse direction of functional chaining. On applying composition, the second function gets executed first and the first function is applied on the result obtained by executing the second function.

To demonstrate,

a.compose(b);

Function b will get executed first and then the function a will be executed on the result returned by function b. Consider an example where we have squares and we know area of squares and we need to know the length of one side of square.

Let us create a functional interface

public interface Function<T, R> {
     R apply(T t);
    default<V> Function<V, R> compose(Function<V, T > before) {
            return (V v -> apply(before.apply(v)); 

}
public class FunctionalComposition {
      public static void main(String[] args) {
           Function<Square, Integer> fun1 = s -> s.getArea();
           Function<Integer, Double> fun2 = area -> Math.sqrt(area);
           
           Function<Square, double> getside =fun2.compose(fun1);
           
          Square s = new Square();
           s.setArea(100);
           
          Double side = getSide.apply(s);
           System.out.println(side);
   }
}

Square function:

public class square {
private int area;
public int getArea() {
return area;
}
public void setArea(int area) {
            this.area = area;
}
}

Output: 10   // side of the square

Closures

The next technique that we will discuss is closures which are defined as a function that refers to free variables in its lexical context.

Here a free variable is an identifier that is used but not defined. A lexical context or a lexical scope allows us to set the scope of a variable.

If functions are passed from one place to another in the code, we have to deal with closures.

Let us take an example which takes a value and prints it.

Let us create an interface

@FunctionalInterface
public interface Task {
     void doTask();
}

Now let us implement this interface by writing a Lambda.

public class JavaClosure {
            public static void main(String[] args) {
                   int val = 111;    
                    Task lambda = () -> {         
                     System.out.println(val); 
                     System.out.println("Task completed")
                   };       
            printValue(lambda);    
            }        
         private static void printValue(Task lambda) {     
              lambda.doTask();
        }
}         

On executing, the output will be:

111

Task Completed

The value is coming from closure. Whenever any lambda expression gives us a free variable in the same scope, the value of that variable is saved and when we call lambda, it goes along with that.

Currying

Now let us discuss about Currying, a technique which restructures a multi – parameter function into multiple functions that have one parameter each.

Lets take an example:

import java.util.function.Function;
public class Currying {
            public static void main(String[] args) {
                        Function<Integer, Function<Integer, Integer> > fun1 = u -> v ->     u + v;
                        Function<Integer, Integer > fun2 = fun1.apply(1);
                        Integer sum = fun2.apply(2);
                        System.out.println(sum);
}
}

This will give output as: 3

We can also modify it by giving the value one after another and still obtain the same output.

import java.util.function.Function;
public class Currying {
            public static void main(String[] args) {
                        Function<Integer, Function<Integer, Integer> > fun1 = u -> v -> u + v;
                        Integer sum = fun1.apply(1).apply(2);
                        System.out.println(sum);
}
}

This will give the same output as: 3

Lazy Evaluation

One more advantage of using Currying is that it allows us to reuse existing interfaces like Function to express functions with multiple values.

Now lets move on to lazy evaluation.

We have been using lazy evaluation ever since we started using Lambdas. Lambdas are not executed when as soon as we write that code. They are executed only when that code is invoked.

Lazy evaluation is an evaluation strategy where the evaluation of an expression is delayed until that value is needed. We can take advantage of this as some expression might never be nedded to be evaluated of some function do not need to be invoked.

for example,

if( this condition is true){
supplier.get();
}
else{
// Do something else
}

In the above code, the supplier Lambda will only be invoked if the condition preceding it is true. Otherwise, that Lambda will not be evaluated.

This laziness also helps in creation of heavyweight objects. We can create instances just when we need them and term them as lazy initialization. In this way, we can minimize resource consumption by avoiding unneeded computations.

Tail Call Optimization/recursion

First let’s see how regular recursion works:

public static void main(String[] args) {
}
public static long refact(int n) {
    if (n  <=  1)
                        return 1;
            else
                        return n * refact(n - 1);
}
}

It will work as

4 * refact(3)

3 * refact(2)

2 * refact(1)

Base condition reached

1

solution = 4*3*2*1*=24

In the case of Tail Recursion;

public static long tailReFact(int n, int a) {
if (n  <=  1)
                        return a;
            else
                        return tailReFact(n-1,n*a);
}
}

It will work as

tailReFact(3, 4)

tailReFact(2, 3*4)

tailReFact(1, 2*12)

Base condition reached

1

solution = 2*12 = 24

When the input is large, regular recursion can result in stack overflow error, whereas in the case of tail recursion method we use an accumulator to hold on to the stack for the operations. That is why a compiler can convert a Tail Recursion into a Pure Iteration and that is called as tail call optimization in compiler. But converting a regular recursive call into iteration is not possible. Compilers just cannot do it.

In functional programming, we use recursion a lot. Functions within function should be used carefully so as to not have the stack overflow problem. In java, we should use iterative approach wherever possible. 

To summarize, we discussed in this blog we first discussed the concepts of Functional Programming. Then we applied these concepts and discussed the techniques and using these techniques we learnt how to write functional APIs.