Wednesday, November 12, 2014

Java 8 : Not a Paradigm Shift. Just Revisiting the Old Roots 2

This post is a continuation of previous post.

Recently I was privileged enough to see two great talks by two Industry Experts. Namely Jessica Kerr and Dr. Venkat Subramaniam and I have the links to those talks at references section and I strongly advise to watch those. First I watched Jessica Kerr's talk on Functional Principles for Object-Oriented Developers and secondly I watched Dr. Subramaniam's talk on Java 8 Language Capabilities. What's in It for You?. I am really glad that I watched those videos in that order because it made me understand the problems prior to Java 8 and how Java 8 has solved those problems elegantly.

Main takeouts from Jessica's talk are the followings;
  1. Function/Methods should not modify shared mutable states.
  2. Function/Methods should not modify input parameters.
  3. Function/Methods should not do harm to the world.
  4. Function/Methods should not change the execution flow (Exceptions must be returned as data not thrown).
She clearly goes through the benefits of being functional and how it can be achieved in different languages including Java prior to Java 8 using Google Guava library. She further explains that our code must move away from being Imperative to being more Functional/Declarative. In the sense we must say what we are need to do but not how to do it. One great example she points out is SQL. SQL language is highly declarative where we don't care how Oracle, MySQL, MS SQL RDBMS's store data underneath. We just say I want all the data of customers by just issuing "SELECT * FROM Customers". This is a really strong concept because this gives the actual Oracle, MySQL, MS SQL implementations to change transparently without ever breaking the client code. Those RDBMS's can retrieve data sequentially, parallel or in any other form. More on this through the words of Jessica herself;

"The power of SQL's declarative style isn't about swapping out Oracle for MySQL; any standard can do that. It's more about, for a given query, the database is free to swap out its strategy based on the shape of the data.
Say you want to count customers in the UK with open orders. (approximate syntax, crude table design)

SELECT COUNT(DISTINCT customer_id) FROM customers JOIN orders ON customer_id
WHERE orders.status = 'OPEN'
AND customer.country = 'UK'

If both tables are small, the database might start with the join and then filter.
If the order table is huge but very few of them are OPEN, the database might start by finding open orders, then connect them to customers, then filter again.
If the customers table is partitioned, the database might split up the table and run in parallel on each partition.

This is the power of a declarative style, of telling the database WHAT to do, not HOW to go about it. All within one underlying database implementation!"

Dr. Subramaniam's talk further verifies the claims of Jessica and explains in detail how Java 8 allows Developers to make the transition from being Imperative to being more Functional/Declarative by introducing Lambda Expressions and Streams. Lambda Expressions makes functions act also as first class citizens allowing Functional/Declarative style coding. One code excerpt I took from Dr. Subramaniam's talk is the following;

import java.util.List;
import java.util.Arrays;
import java.util.function.Consumer;

public class Sample {

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        //Imperative
        //External Iteration

        for(int i = 0;i<numbers.size();i++) {
            System.out.println(numbers.get(i));
        }

        for(int e:numbers) {
            System.out.println(e);
        }

        //Internal Iteration

        numbers.forEach(new Consumer<Integer>() {
            public void accept(Integer no) {
                System.out.println(no);
            }
        });

        numbers.forEach((Integer no) -> System.out.println(no));

        numbers.forEach((no) -> System.out.println(no));

        numbers.forEach(no -> System.out.println(no));

        numbers.forEach(System.out::println);

        //Functional/Declarative
    }
}

The above code can be described as the blue print of Imperative to Functional/Declarative evolution in Java. The code begins being highly Imperative where we need to tell how to loop a Collection of numbers then it gradually evolves to become a highly Functional/Declarative where Iteration and Logic is handed over to the Collection itself. All the codes print out the same results.

Furthermore he shows how mutations takes place with Imperative code and how we can eliminate mutations by making use of Functional/Declarative style of coding in Java 8. Following code describes;

import java.util.List;
import java.util.Arrays;
import java.util.function.*;

public class Sample1 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Imperative
        int total = 0;
        for(int e:numbers) {
            total += e;
        }    
        System.out.println(total);

        // Functional/Declarative
        System.out.println(numbers.stream()
                                  .reduce(0, (c, e) -> { return c + e; }));

        // Imperative
        total = 0;
        for(int e:numbers) {
            if(e % 2 == 0) {
                total += e;
            }
        }     
        System.out.println(total);

        // Functional/Declarative
                System.out.println(numbers.stream()
                                          .filter(e -> e % 2 == 0)
                                          .reduce(0, (c, e) -> { return c + e; }));
        
        // Imperative
        total = 0;
        for(int e:numbers) {
              total += (e * 2);
        }     
        System.out.println(total);

        // Functional/Declarative
                System.out.println(numbers.stream()
                                          .map(e -> e * 2)
                                          .reduce(0, (c, e) -> { return c + e; }));

        // Imperative
        int no = 0;
        for(int e:numbers) {
             if(e > 3 && e % 2 == 0) {
                 no = e;
                 break;
             }  
        }     
        System.out.println(no);

        // Functional/Declarative
        System.out.println(numbers.stream()
                                  .filter(e -> e > 3)
                                  .filter(e -> e % 2 == 0)
                                  .findFirst()
                                  .orElse(0));

        // Imperative
        System.out.println(total(numbers, e -> true));
        System.out.println(total(numbers, e -> e % 2 == 0));
        System.out.println(total(numbers, e -> e % 2 == 1));

        // Functional/Declarative
        System.out.println(total2(numbers, e -> true));
        System.out.println(total2(numbers, e -> e % 2 == 0));
        System.out.println(total2(numbers, e -> e % 2 == 1));

    }

    public static int total(List<Integer> numbers, Predicate<Integer> condition) {
        int total = 0;
        for(int e:numbers) {
            if(condition.test(e)) {
                total += e;
            }
        }
        return total;
    }

    public static int total2(List<Integer> numbers, Predicate<Integer> condition) {
        return numbers.stream()
                      .filter(condition)
                      .reduce(0, (c, e) -> c + e);
    }
    
}

The fact of the matter is Imperative code with mutations can cause headaches and hair loss if they need parallelism. Where as the Functional/Declarative style of coding in the above code can be parallelized by just changing "numbers.stream()" to "numbers.parallelStream()". That's it.

This is because in Functional/Declarative we are saying what needs to be done not how to do it. So how to do it can be decided by the JVM just as how RDBMS decide how to retrieve data for a particular SQL query. Furthermore Functional/Declarative style is combined with Lazy (Efficient) execution where there are Intermediate (map, filter) and Terminal (reduces, findFirst) functions. The Stream will not be evaluated until we call a Terminal function making our code efficient.

Both of the speaker agree on one thing. Familiar code is not necessarily Readable code. Just because we have written for loops to iterate a zillion times doesn't mean it is not complex. Don't be fooled by thinking that because Java 8 Functional/Declarative style of coding is Complex because it looks Unfamiliar. May be it is the best and efficient way to do what you really want to do.

I would like to end this by a quote by Dr. Subramaniam, he states "The biggest change in Java 8 are in the minds of the programmers".

References
  1. Functional Principles for Object-Oriented Developers - By Jessica Kerr
  2. Java 8 Language Capabilities. What's in It for You? - By Dr. Venkat Subramaniam

No comments:

Post a Comment