Saturday, November 18, 2017

Lambda Expression / Functional Programming - Not Always Beautiful.

Functional Programming has been gaining much attentions in the past few years.  Java 8 started to support the lambda expression and several new programming languages adopted the functional programming.  While I also have been learning it and watching many developers saying how great the functional programming is, I have also been remembering an old story I read before an age 10: The Naked King (or The Emperor's New Clothes)  - Everyone says "the most beautiful clothes" until a pure child says "the king is naked!"

I exaggerated.  Functional programming is very useful and worth to be popular, but it definitely requires careful thought before blindly saying "it is very beautiful".  A blog "Stop abusing lambda expression" also talks about what people need to be careful and I completely agree with this blog.

On this post, I will show you a few things we need to think about as I convert simple algorithm shown on the previous post to a lambda expression in Java 8 and to functions in Scala.

- Minimum Difference of Each Two Numbers in a Sorted Array / List
The previous post "Algorithm, Performance, and Back to the Basic" showed one solution.  When an input array is in a sorted order, this is a simple imperative code..
[Code 1]
public int findMinAbsDifferenceSorted(int[] numbers) {
   int min = Integer.MAX_VALUE;
   for(int i=1; i<numbers.length; i++){
      int diff = numbers[i] - numbers[i-1];
      if(diff < min){
         min = diff;
      }
   }
   return min;
}

- Functional Way
I am not very good at functional programming yet. So, I thought about how I can convert the simple imperative method to a functional way.  Since the functional programming suppose to be simple, less maintainable, and less error prone, this is how I write.  I forced myself to write the method this way, but I think creating another IntStream may not be a good functional way. (Functional programming experts:  Please suggest me the better code)
[Code 2]
public int findMinAbsDifferenceSortStream(int[] numbers) {
   return IntStream.range(0, numbers.length-1)
            .map(i -> numbers[i+1] - numbers[i])
            .min().getAsInt();
}

- Methods in Scala
We can convert the imperative method to functional programing in a few different ways and I will show you two different methods.
Without much thinking, this could be a simple way in Scala
[Code 3]
{for(i <- 1 to numbers.length -1) yield numbers(i) - numbers(i-1)}.min

After some consideration, this could be another way.
[Code 4]
numbers.tail.zip(numbers).map(n => n._1 - n._2).min

As you see, the Code 3 & 4 are much simpler than the Code 2 or Code 1.  Great! Are we happy now?  If you want to say "Yes", please wait until you answer the following questions.

1. Which code is better between Code 3 and Code 4? (Yes, I am not even asking you to think about different languages)
2. What would be a Big O notation on these Code 3 & 4?  Can you anticipate the growth rate?

It is known that Functional Programming focuses on "what to do" instead of "how to do" as like the imperative programming.  So, the Code 3 & 4 shows us "what to do" but is it really enough for software engineers?

- Execution Time of Each Method
Running 5 times with 100,000 random numbers :

Code 3:  22958 ~ 50029 ms
Code 4:  6 ~ 33 ms

Running 5 times with 10,000,000 random numbers :

Code 1:  5 ~ 9 ms
Code 2:  9 ~ 13 ms
Code 3:  Didn't even run it
Code 4:  3262 ~ 11068 ms

I understand that there was some unfairness: Scala uses an Int object and Java code used an primitive 'int' data type.  So, I also used a List<Integer> data type for the Code 1 & 2.  The execution time with 10,000,000 random numbers are

Code 1 with List<Integer>:  21 ~ 30 ms
Code 2 with List<Integer>:  26 ~ 38 ms

- Summary
I showed execution time of each method although I understand the execution time isn't everything.  Functional programming makes an parallel process simpler and we can utilize the usage of a multi core processor.  Nevertheless, I am asking how we can evaluate the algorithm of Code 3 and Code 4 before running the code with larger data set.  This much slower execution time might be not only a matter of an executing algorithm itself, but also using and creating immutable objects.

As we can see from many other resources, the Functional programming has many strengths, but I just want to say that software engineers need to see more than "what to do".  Without seeing "how to do", we may have hard time to control the quality of software. (although the definition of the quality may be different to different people.)

No comments:

Post a Comment

Java 9: Flow - Reactive Programming

Programming world has always been changed fast enough and many programming / design paradigms have been introduced such as object oriented p...