Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Functional Programming: Clean Code and Design Patterns

In this article we will use concepts of clean code and design patterns that we apply in daily programming with Java to understand how functional thinking is much simpler than it looks.

The study of a new paradigm always involves greater effort at the beginning of the journey, once we are presented to many unfamiliar concepts. Functional programming is no exception, however, when we learn something new from examples we already know, the task becomes much easier. In this article we will use concepts of clean code and design patterns that we can apply in daily programming with Java to understand how the functional thinking is much simpler than appears to be, and that can be seen as a natural evolution of good programming practices that we know in Object Orientation.

Behavior "injected"

Let's look at the code in Listing 1.

Listing 1. Loop

import java.util.Arrays;
import java.util.List;

public class IterationExample1 {

  public static void main(String[] args) {
	  List<String> values = Arrays.asList("1", "2", "3");
	  Iterator<String> it = values.iterator();
	  int sum = 0;
	  while(it.hasNext()) {
		  sum += Integer.parseInt(it.next());
	  }
	  System.out.println("Sum = " + sum);
  }
}

In the code we can see the iteration over a list of numbers (represented as strings), where each iteration convert a string to an int item and we accumulate it in the variable sum.

Throughout our career, we have written hundreds of similar codes to this, where we have to iterate over a collection, make some processing on the items and return a result. The iteration operation is universal and what vary from implementation to implementation is the kind of treated data and behavior to be processed for each item.

But, to be universal, the repeated presence of this operation in the code we write offends the DRY principle (Don't Repeat Yourself). Java, from its version 5, released the resource for-each, shown in Listing 2.

Listing 2. Using for-each

import java.util.Arrays;
import java.util.List;

public class IterationExample2 {

  public static void main(String[] args) {
	  List<String> values = Arrays.asList("1", "2", "3");
	  int sum = 0;
	  for(String item: values) {
		sum += Integer.parseInt(item);
	  } 
	  System.out.println("Sum = " + sum);
  }
}

The above code is much more elegant than the code in Listing 1, for now the language itself is the iterator, allowing us to focus on the behavior that should apply to each item to get the desired result, but even so, this does not relieve us of writing the loop.

But from Java 8 on, we can do even better, as shown in Listing 3.

Listing 3. Using stream

import java.util.Arrays;
import java.util.List;

public class IterationExample2 {

  public static void main(String[] args) {
	  List<String> values = Arrays.asList("1", "2", "3");
	  final int sum = values.stream()
		.map(item -> Integer.parseInt(item))
		.reduce(0, (a,b) -> a + b);
	  System.out.println("Sum = " + sum);
  }
}

The code in Listing 3 is the same as the one in Listing 2, but it is written in a very different way from what we are used to.

Where is the iteration operation and the loop? It is now encapsulated in the Java API, no longer being needed to write that logic in our program. In addition to eliminating the iteration, we pass the "behavior" we want to perform into the list as a parameter for two methods, in the case:

  • Convert an integer value to String to the map method;
  • Add the list items to reduce method.

We just write the "declarative" version of the previous program in this new version. What is the difference between this and the previous style (the imperative style)?

The imperative version adopt a sequential approach, where we define step by step everything that the software should do, like the coding of the loop and the rules associated with the processing of the list items.

In the declarative programming we work with flows from composing methods and transfering behavior via lambdas, as shown in the diagram in Figure 1.

Figure 1. Composition methods

We have the following scenario: the map method will work in the Strings list, turning it into a list of Integers (of which this transformation was "injected" via parameter). Next, the output map is processed by the reduce method that will apply operations of sums (and accumulation) in the elements of the list and returns the final result, and the sum operation was also parameterized. We can therefore conclude that:

1) The map method always applies a transformation into a collection while maintaining the same number of elements as shown in the Figure 2 scheme.

Figure 2. map method

2) In the other side, reducemethod "reduces" a collection to a single value.

Watch the gains on the issue of clean code of the declarative version:

  • DRY: By abstracting the loop, we eliminate duplication of this operation in our codes.
  • Separation of concerns: The entire logic of "infrastructure", ie the creation of the loop and its iteration shall be borne by the API, while the developer focuses more on behavior to be applied to items in the collection.
  • Inversion of Control: We injected the behavior we want in the functional methods of Stream (map, reduce, filter, etc.) via parameter, making these highly flexible methods.
  • Composition and chaining: define the overall behavior of the program from the combination of methods and lambdas.

We already know all these quality patterns and we try to apply them in our day to day, with the advantage of functional programming is that all this comes already within. If we stop to think, sometime in the processing of the internal code of map via Stream, the Java API will have to iterate, after all our computers are built from the Von Neumann architecture, where a statement is processed at a time, ie, it is imperative.

The FunctionalParadigmcan be seen as a high-level abstraction on the imperative programming, and a set of techniques and concepts designed to restrict the most changeable states and side-effects within the program (Restrictive Programming) and thus facilitate the creation of concurrent and reactivesystems.

The current technology scenario walks to multi-core processors, clusters, reactive programming, distributed in the clouds and Big Data, where the writing of concurrent applications that take full advantage of hardware resources is less error prone when adopting FunctionalProgramming.

Standards and Functional Paradigm

The "behavior injection" appears to be a new idea, that only exists in functional languages, but this is not true, because even without the support of Lambdas we can pass variable behavior for a method or class from interfaces, for example. A very familiar case is the Runnable interface, present in Listing 4.

Listing 4. Runnable

new Thread(new Runnable() {
   @Override 
   public void run() {} 
}); 

Did you ever stop to think about the complexity of creating a thread in the Java Virtual Machine? Probably not, and the responsible for this is the Thread class, which abstracts the issue magically creating a thread that will run our code. We just have to pass in its constructor an object that inherits from Runnable interface and implements its only method run, as shown in Listing 4. That is, a Thread class can perform any type of behavior that we define, in parallel, without which we having to worry on how this is done in fact (being very similar to the use of Stream methods previouslyseen).

In addition, to implement variable behavior we can use Design Patterns such as Strategy, State, Template Method, Visitor or other behavioral pattern.

Let's look at a simple implementation of the Strategy pattern in Listing 5.

Listing 5. Implementing Strategy

interface Strategy {
  double apply(double a, double b);
};

class Maximum implements Strategy {
  @Override
  public double apply(double a, double b) {
	  return a > b ? a : b;
  }
};

class Minimum implements Strategy {
  @Override
  public double apply(double a, double b) {
	  return a < b ? a : b;
  }
};

class Average implements Strategy {
  @Override
  public double apply(double a, double b) {
	  return (a + b) / 2;
  }   
};

class Context {
  private final Strategy strategy;

  public Context(Strategy strategy) {
	  this.strategy = strategy;
  }

  public double execute(double a, double b) {
	  return this.strategy.apply(a, b);
  }
};

public class StrategyExample {    
  public static void main(String[] args) {        
	  Context context = new Context(new Maximum());
	  System.out.println(context.execute(3,4));

	  context = new Context(new Minimum());
	  System.out.println(context.execute(3,4));

	  context = new Context(new Average());
	  System.out.println(context.execute(3,4));  
  }    
}

Note that we have defined a Context class and three behaviors that it can adopt, in this case Maximum, Minimum or Average of two values, and in the main method we applied all these behaviors.

This code follows the Open/Closed Principleof SOLID, because we can define several other types of behaviors (addition, subtraction, division, etc.) for the Context class internally without having to change your code (open to extensions and closed for modifications).

Now let's compare the same example, using Lambdas, in Listing 6.

Listing 6. Strategy with Lambdas

@FunctionalInterface
interface Strategy {
  double apply(double a, double b);
};

public class StrategyFunctionalExample {
  public static double execute(double a, double b, Strategy strategy) {
	  return strategy.apply(a, b);
  }
  
  public static void main(String[] args) {
	  System.out.println(execute(3, 4, (a,b) -> a > b ? a : b ));
	  System.out.println(execute(3, 4, (a,b) -> a < b ? a : b ));
	  System.out.println(execute(3, 4, (a,b) -> (a + b) / 2 ));
  }
}

The code makes visible the amount of verbiage that we eliminated when using the support to Lambdas and Functional Interfaces of Java 8, generating a lot cleaner and more concise code.

One of the biggest obstacles of previous versions is the verbosity of the language, which sometimes even discourages the use of standards by some programmers, but even so, you can program "parameterized behavior" and inject it through interfaces and Design Patterns.

The support for Lambdas on Java 8, therefore, did not inaugurate the technique of "injecting behavior", but enables the application of standards and SOLID principles in a much more easily way than in previous versions, and the Strategy pattern (and also the Template Method) is an important item of the Functional Paradigm, because methods like map, filter, reduce, flatMap, sorted, collect use the concept of receiving parameterized behavior without hurting the principle of Open/Closed.

All of these methods mentioned above are known in the functional means as higher-order functions. Despite the fancy name, it only relates to methods receiving lambda expressions as parameters or returning lambdas. But "behind the scenes", Java turns all lambdas expressions into concrete classes to maintain compatibility with the previous versions, and it is, therefore, just "syntactic sugar" to reduce the verbosity of the language.

Immutability and Side-Effects

If the Object Oriented Programming, immutability is already recommended. In the Functional Programming it is fundamental.

In the Functional Programming, as can be inferred by the name, the key building blocks are the functions, which ideally should have the same behavior of mathematical functions, ie, the result of a function is determined solely by its(s) inlet(s) without other external factors that can alter the outcome. Moreover, you should not produce side effects, such as:

  • Changing the state of an input variable (for example, mutable objects);
  • Change the value of an external variable to it;
  • To produce an effect of I / O (write file, etc.).

Functions following these premises are known as pure functions and are the main goal of functional languages. Realize how much pure functions are intertwined with the best programming practices:

  • A function should perform only one task (the Single Responsibility Principle);
  • New behaviors are created from the composition of most basic functions (code reuse);
  • Pure functions are thread safety, because they generate no side effects and thus can be safely used in concurrent programming and, depending on the situation, even in parallel;
  • Have low coupling and high cohesion.

Furthermore, the pure functions are modular as well. Let's look at a real example with Unix commands

There are several types of shell tools in Unix: cat, find, sed, awk, grep, tail, head, etc. Each of these programs plays a clear role, and can make various kinds of combination between them, generating various types of functionality never imagined by the creators of these tools, as shown in Figure 3.

Figure 3. Pipeline of Unix commands

In the Unix command model presented, the output from an application becomes the input of another. This is very similar to the diagram of Figure 1, and is just that. Java 8 can do this "pipeline" through Stream, as shown in Listing 7.

Listing 7. "Pipeline"

import java.util.ArrayList;
import java.util.List;

public class Pipeline {    
  
  public static void main(String[] args) {
	  List<Integer> list = new ArrayList<>();
	  for(int i = 0; i < 100; i++) list.add(i);
	  
	  // Pipeline
	  int sum = list.stream()
		  .filter(i -> i % 2 == 1)
		  .map(i -> i * 2)
		  .reduce(0, (a,b) -> a + b);
			  
	  System.out.println("Sum = " + sum);
  }    
}

The Java program behavior is identical to that of the Unix commands pipeline, once wih the composition for methods and lambda, it was possible to assemble the declarative logic of the program.

The composition of functions reminds us the model "lego" (see Links section), where the creation of a program is seen as the assembly of building blocks to create more complex structures.

To avoid as much as possible creating impure functions, working with immutable objects in Java is critical because the levy is checked by the compiler, ensuring that the programmer does not make the mistake of changing the state of an object passed as an argument to the method.

The Stream class is a monad (see section Links), just like Optional and CompletableFuture, and the most interesting aspect of this in Java is the point of the composition, once the calls to filter and map methods in the previous example return a Stream, ie, from the initial monad list.stream we apply methods and generate other Streammonadsuntil the reduce operation converts it in an integer value. In the case of Optional and CompletableFuture, operations involving flatMap and map allow you to write more concise and declarative code to string together various types of operations on them.

Hiding complexity

Working with Stream we care more about how to program the behavior, leaving the whole loop part and control for the Java API, this being very similar to what we do with Thread: let the API care of the most complex aspects and leaves us focus more on business rules.

Leaving these details in charge of the Java API and the Oracle engineers enables them to optimize the most these codes, applying various complex techniques to improve performance, memory consumption, parallelism, etc. Take the example in Listing 8.

Listing 8. The set of natural numbers

import java.util.stream.IntStream; 
public class InfiniteExample { 
   public static void main(String[] args) { 
      long sum = IntStream.iterate(0, n-> n+1).sum(); 
      System.out.println("sum = " + sum); 
   }
}

Note that we are using the iterate method ofIntStreamfor generating a representation of the natural numbers (0, 1, 2, 3, 4 ...) by the lambda function n -> n + 1 (where 0 is the initial element, passed as the first parameter), and summing the whole set via sum method. As the set of natural numbers is infinite, this program never terminates.

Now let's apply the limit method, as shown in Listing 9.

Listing 9. Using limit

import java.util.stream.IntStream;

public class FiniteExample {

  public static void main(String[] args) {
	  long sum = IntStream.iterate(0, n-> n+1)
		.limit(4)                        
		.sum();
	  System.out.println("sum = " + sum);        
  }
}

Note that we are limiting the pool to four elements, in this case 0|1|2|3, and adding them from sum, obtaining the result of 6. Note that the Stream code takes into account not only the iterate method, but also the limit, ie, the evaluation sentence is not linear, sequential, like in the imperative mode. The Stream code deals lazily with chaining methods, hiding the more complex details of this technical developers.

In other words, to adopt the declarative style of programming we are sure that the infrastructure code will be the most performative possible assuring that possible future changes in the JVM or in the language impacts in the rewriting of our code, especially with regard to improvements in support paralleling of Stream. The same goes for monads mentioned above, especially CompletableFuture, which has the premise to ensure that the higher-order functions combined do not affect the asynchronous aspect of its operations.

Conclusion

The support of Java 8 to some of these functional capabilities simply attests to Oracle's commitment to not let language become obsolete, allowing a wide range of Java developers take advantage of the advantages of this new paradigm. The fact that Java is an object oriented language allows interesting combinations of concepts between these two paradigms

Thank you and see you soon!

Links

Trampoline: https://gist.github.com/pkukielka



Fabrí­cio Galdino is a software expert and has worked with IT analysis and business development for more than five years. It has extensive experience with testing, back and front-end technologies.

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login