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

Getting Started with Java Monads

In this article we will explore various concepts of Monads, because the seed of this topic is Functional Programming. This is a survival guide for those starting in the area.

The study of the concept of Monad, at first glance, may seem a daunting task for those who are beginning to study the functional paradigm. In this article we demystify some points around this theme to make it more accessible and practical.

Pipeline and Mathematics

Let's see Listing 1 program.

Listing 1. Method calls

 package example01;
   
  public class Example01 {
      
      public static double multiplyBy2(double n) {
          return n * 2;
      }
      
      public static double divideBy3(double n) {
          return n/3;    
      }
      
      public static double round(double n) {
          return Math.round(n);
      }
      
      public static double applyOperation(double n1) {
          final double n2 = multiplyBy2(n1);
          final double n3 = divideBy3(n2);
          final double n4 = round(n3);
          return n4;
      }
   
      public static void main(String[] args) {                
          System.out.println("Output = " + applyOperation(12));
      }    
  } 

Note that from an input data n1, we apply various transformations (multiplication, division and rounding) to obtain the final result. Note that within the applyOperation function (use the term function and method without distinctions in this article), the output of a function is the input of the next.

We can say that applyOperation is purely a composite function, as it was assembled exclusively from the chaining of other functions. This is very common in imperative languages and is one of the key schemes for Functional Programming.

This means that a pipeline in mathematical terms?

Let us generalize the example of Listing 1: given three functions f(x), g(x) and h(x) can represent the pipeline as follows:

 a = f (x); b = g (a); c = H (B); or just h (g (f (x)))

The notation h(g(f(x))) is the mathematical representation of a pipeline of three functions. Let's rewrite the applyOperation function in terms of this expression, as shown in Listing 2.

Listing 2.Mathematical Pipeline

package example02;
   
  public class Example02 {
      
      public static double multiplyBy2(double n) {
          return n * 2;
      }
      
      public static double divideBy3(double n) {
          return n/3;    
      }
      
      public static double round(double n) {
          return Math.round(n);
      }
      
      public static double applyOperation(double n1) {
          return round(divideBy3(multiplyBy2(n1)));
      }
   
      public static void main(String[] args) {                
          System.out.println("Output = " + applyOperation(12));
      }    
  }

Semantically speaking, the functions of applyOperation Listings 1 and 2 are identical, and the code in Listing 2 makes clear the "signature" math pipeline and removes the use of temporary variables. However, use this type of notation greatly impairs the readability of the program, just imagine a pipeline with ten functions.

Functional Programming in the basic building blocks are the functions and composition that we saw in the applyOperation function is a very common technique that helps promote code reuse.

So we have the following quandary: how to represent compositions method in an elegant way and easy to read without getting using temporary variables, as in the example in Listing 1? This is where the concept of Monad.

Monads and Pipeline

Note the code in Listing 3.

Listing 3. Using the Optional Monad

package example03;
   
  import java.util.Optional;
   
  public class Example03 {
      
      public static Optional<Double> multiplyBy2(double n) {
          return Optional.of(n * 2);
      }
      
      public static Optional<Double> divideBy3(double n) {
          return Optional.of(n/3);    
      }
      
      public static Optional<Double> round(double n) {
          return Optional.of(Double.valueOf(Math.round(n)));
      }
      
      public static Optional<Double> applyOperation(double n1) {
          return multiplyBy2(n1)
                    .flatMap(n -> divideBy3(n))
                    .flatMap(n -> round(n));
      }
   
      public static void main(String[] args) {                
          System.out.println("Output = " + applyOperation(12).get());
      }    
  }

The code in Listing 3 is exactly the same as the one in Listing 2, but using Optional. Realize that graphically in Figure 1 remains valid to represent the pipeline of applyOperation function operations, because the role of a Monad is exactly this: apply the composition through a flat threaded writing (flat) a level, being much more readable than the mathematical notation nested multiple levels h(g(f(x))).

Then, instead of h(g(f(x))), we have: f(x).flapMap(y =>g(y)).flapMap(z=>h(z)), or make monads more readable and natural reading sequence of steps (from left to right) applied to the original input data.

This is the biggest reason for their presence is so remarkable in functional languages, as to be considered a standard of functional design and be the support base of all the declarative programming of Java 8 (Optional, Stream and CompletableFuture) along with support the Lambdas and Functional Interfaces.

Map and flatMap

According to the definition of Martin Ordersky, creator of Scala, Monad is a parameterized type that must have at least two major operations:

  • unit: put the value into the monad (container);
  • flatMap: allows nested calls;
  • ** Map: although not part of the definition, this method usually appears in conjunction with flatMap is highly recommend it available when you create a Monad (we shall see the reason).

The unit method is represented by the method of the Optional, which puts the parameter into the monad (container). Note that some monads can not have unit methods, but use others to do the same thing (of) or own builder to include the value directly in the container.

Already flatMap allows calls chained and gets a lambda expression as a parameter, and the following code to your signature:

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)

What is the purpose of a flatMap function of a Monad? Consider the following example in Listing 4, which we will use to explain the map flatMap.

Listing 4. Using map

package example04;
   
  import java.util.Optional;
   
  public class Example04 {
      
      public static void main(String[] args) {                
          Optional<Integer> optionInt = Optional.of(10);
          Optional<String> optionStr = optionInt.map(i -> i + "");
          System.out.println("Output = " + optionStr.get());
      }    
  }

See map we use to apply a transformation from Integer to String. Every function map can be plotted in the same manner as shown in Figure 1.

Figure 1. Graphical representation of map

As a monad of n elements, we can map set as a transformation applied to the set X, generating the corresponding elements Y, from the use of a function (lambda) transforming F(x), while maintaining the same number of elements.

Notice an important detail: when applying map on a monad, the transformation of the input data is made, and the result is encapsulated again in Monad. In Listing 4 it is clear since map was applied to the Monad Optional, and, as the lambda function converts an Integer to String, map returns the result in the same Monad Optional, only changing the type to Optional.

Figure 2. Map

Figure 2graphically depicts the map behavior. It is this characteristic of preserving the monad allowing successively applying map operations (and flatMap) concatenating the output of a lambda as the next entry. Let's rewrite the example in Listing 3 using map, as shown in Listing 5.

Listing 5. Pipeline with map

package example04;
   
  import java.util.Optional;
   
  public class Example04 {
      
      public static Double multiplyBy2(double n) {
          return n * 2;
      }
      
      public static Double divideBy3(double n) {
          return n/3;
      }
      
      public static Double round(double n) {
          return (double) Math.round(n);
      }
      
      public static Optional<Double> applyOperation(double n1) {
          return  Optional.of(n1)
                    .map(n -> multiplyBy2(n1))
                    .map(n -> divideBy3(n))
                    .map(n -> round(n));
      }
   
      public static void main(String[] args) {                
          System.out.println("Output = " + applyOperation(12).get());
      }    
  }

The program of Listing 3 and 6 preserve the pipeline of Figure 1, and uses a flatMap and the other map, so both are used to create fluent composition lambda expressions.

Now let's look at the following example in Listing 6.

Listing 6. Map nestled

package example05;
   
  import java.util.Optional;
   
  public class Example05 {
      
      public static Optional<Double> multiplyBy2(double n) {
          return Optional.of(n * 2);
      }
      
      public static Optional<Double> divideBy3(double n) {
          return Optional.of(n/3);
      }
      
      public static Optional<Double> round(double n) {
          return Optional.of((double) Math.round(n));
      }    
   
      public static void main(String[] args) {                
          Optional<Optional<Double>> optMap = multiplyBy2(12).map(n -> divideBy3(n));
          Optional<Double> optFlatMap = multiplyBy2(12).flatMap(n -> divideBy3(n));        
      }    
  }

Notice that the line:

Optional<Optional<Double>> optMap = multiplyBy2(12).map(n -> divideBy3(n));

The map function applies a transformation preserving the Monad of origin, as shown in Figure 3.

Figure 3. Map the Monad source

However, as the return divideBy3(12) is Optional, we have an Optional nesting, as Figure 4shows.

Figure 4. Optional nesting

The figure presented makes clear the problem to use map with lambda expressions that return monads. To solve this we must use flatMap because it applies the operation "map" and then "flatten" (flatten, level), removing the nesting of the two monads, as shown in Figure 5.

Figure 5. "Flatten"

An important fact to note is that only accepts flatMap receive lambdas expressions that return the same Monad source (the parameterized type can be different), while map accepts lambdas returning anything, including other monads, as the example in Listing 7.

Listing 7. Map and flatMap

package example06;
   
  import java.util.Optional;
  import java.util.concurrent.CompletableFuture;
  import java.util.stream.Stream;
   
  public class Example06 {
   
      public static void testMap() {        
          Optional<Integer> optInt = Optional.of(1);
          Optional<Stream<Integer>> optStreamInt = optInt.map(n -> Stream.of(n));
          Optional<CompletableFuture<Stream<Integer>>> optStreamComplInt =                
              optStreamInt.map(x -> CompletableFuture.supplyAsync(() -> {
                  return x;
              }));                        
      }    
      
      public static void testFlatMapOk() {        
          Optional<Integer> optInt1 = Optional.of(1);
          Optional<Integer> optInt2 = optInt1.flatMap(n -> Optional.of(n+1));
          Optional<String> optInt3 = optInt2.flatMap(n -> Optional.of(String.valueOf(n+1)));
      }            
      
      public static void testFlatMapError() {        
          Optional<Integer> optInt = Optional.of(1);
          Optional<Stream<Integer>> optStreamInt = optInt.flatMap(n -> Stream.of(n));
          Optional<CompletableFuture<Stream<Integer>>> optStreamComplInt =                
              optStreamInt.flatMap(x -> CompletableFuture.supplyAsync(() -> {
                  return x;
              }));                        
      }            
  }

When working with composition lambdas expressions, the correct is to always use the same Monad to avoid strangers nesting of these, as we saw in Listing 7, and enable the use of flatMap.

Monads and "happy path"

Monads can be used to implement the concept of "happy path", as we can see in the example with Optional Listing 8.

Listing 8. Avoiding NullPointer

package example07;
   
  import java.util.Optional;
  import java.util.Random;
   
  public class Example07 {
      
      public static Optional<Double> randomOptional(Double value) {
          return Optional.ofNullable((new Random()).nextBoolean() ? value : null);
      }    
      
      public static Optional<Double> multiplyBy2(double n) {
          return randomOptional(n * 2);
      }
      
      public static Optional<Double> divideBy3(double n) {
          return randomOptional(n/3);
      }
      
      public static Optional<Double> round(double n) {
          return randomOptional((double) Math.round(n));
      }
      
      public static Optional<Double> applyOperation(double n1) {
          return  Optional.of(n1)
                    .flatMap(n -> multiplyBy2(n1))
                    .flatMap(n -> divideBy3(n))
                    .flatMap(n -> round(n));
      }
   
      public static void main(String[] args) {        
          Optional opt = applyOperation(12);
          if(opt.isPresent()) {        
              System.out.println("Output = " + opt.get());
          }
      }     
  }

In Listing 8 code, any of the functions can return Optional.ofNullable(null), and when this happens, both map as flatMap continue chaining, but without processing the following lambdas expressions to the expression that generated the null result. To facilitate understanding, let's put prints in the code as shown in Listing 9.

Listing 9. Using prints

package example08;
   
  import java.util.Optional;
  import java.util.Random;
   
  public class Example08 {
      
      public static Optional<Double> randomOptional(Double value) {
          Optional<Double> opt = Optional.ofNullable((new Random()).nextBoolean() ? value : null);
          if(!opt.isPresent()) {
              System.out.print(" - Null");    
          }
          return opt;
      }    
      
      public static Optional<Double> multiplyBy2(double n) {
          System.out.print("\nmultiplyBy2");
          return randomOptional(n * 2);
      }
      
      public static Optional<Double> divideBy3(double n) {
          System.out.print("\ndivideBy3");
          return randomOptional(n/3);
      }
      
      public static Optional<Double> round(double n) {
          System.out.print("\nround");
          return randomOptional((double) Math.round(n));
      }
      
      public static Optional<Double> applyOperation(double n1) {
          return  Optional.of(n1)
                    .flatMap(n -> multiplyBy2(n1))
                    .flatMap(n -> divideBy3(n))
                    .flatMap(n -> round(n));
      }
   
      public static void main(String[] args) {        
          Optional opt = applyOperation(12);
          if(opt.isPresent()) {        
              System.out.print("\nOutput = " + opt.get());
          }
          System.out.println();
      }     
  }

Let's look at the possible ways in Listing 9.

// Output 1
  multiplyBy2
  divideBy3
  round – Null
   
  // Output 2
  multiplyBy2 – Null
   
  // Output 3
  multiplyBy2
  divideBy3 – Null
   
  // Output 4
  multiplyBy2
  divideBy3
  round
  Output = 8.0

Note that the lambda expressions are executed until the first null to appear, and the subsequent expressions are not processed (similar to the resource if). But notice that the thread always comes to an end, regardless of null or not appear (hence the happy path name). Then just test the final result of the pipeline to see if it failed or not (fail here if means if any of the composition of functions had to deal with null).

This property of Monad Optional allows you to write a much more compact code and elegant than the option of staying testing all returns to see if they are null or not. But not only Optional that has this feature. We will see in Listing 10 to rewrite the previous application using Stream.

Listing 10. Using Stream

package example09;
   
  import java.util.Random;
  import java.util.stream.Stream;
   
  public class Example09 {
      
      public static Stream<Double> randomStream(Double value) {        
          if(new Random().nextBoolean()) {
              return Stream.of(value);
          } else {
              System.out.print(" - Empty");
              return Stream.empty();
          }
      }    
      
      public static Stream<Double> multiplyBy2(double n) {
          System.out.print("\nmultiplyBy2");
          return randomStream(n * 2);
      }
      
      public static Stream<Double> divideBy3(double n) {
          System.out.print("\ndivideBy3");
          return randomStream(n/3);
      }
      
      public static Stream<Double> round(double n) {
          System.out.print("\nround");
          return randomStream((double) Math.round(n));
      }
      
      public static Stream<Double> applyOperation(double n1) {
          return  Stream.of(n1)
                    .flatMap(n -> multiplyBy2(n1))
                    .flatMap(n -> divideBy3(n))
                    .flatMap(n -> round(n));
      }
   
      public static void main(String[] args) {        
          Stream stream = applyOperation(12);
          stream.forEach(n -> System.out.print("\nOutput = " + n));
          System.out.println();
      }     
  }

In the code shown, the concept of "happy path" and short circuit still present, only instead of null, use Stream.empty (both concepts represent no value). The most interesting aspect is that we exchange Optional for Stream without changing the program semantics, because any monad can be used to provide pipelines.

The short circuit occurs when the subsequent concept lambda expressions are not processed when there is an empty occurrence in any one of lambda expressions.

Of course, to prove this statement, could not miss the example with CompletableFuture, as shown in Listing 11.

Listing 11. CompletableFuture

package example09;
   
  import java.util.Random;
  import java.util.concurrent.CompletableFuture;
   
  public class Example09 {
      
      public static CompletableFuture<Double> randomCompletableFuture(Double value) {        
          if(new Random().nextBoolean()) {            
              return CompletableFuture.completedFuture(value);
          } else {
              System.out.print(" - Empty");
              CompletableFuture future = new CompletableFuture();
              return new CompletableFuture();
          }
      }    
      
      public static CompletableFuture<Double> multiplyBy2(double n) {
          System.out.print("\nmultiplyBy2");
          return randomCompletableFuture(n * 2);
      }
      
      public static CompletableFuture<Double> divideBy3(double n) {
          System.out.print("\ndivideBy3");
          return randomCompletableFuture(n/3);
      }
      
      public static CompletableFuture<Double> round(double n) {
          System.out.print("\nround");
          return randomCompletableFuture((double) Math.round(n));
      }
      
      public static CompletableFuture<Double> applyOperation(double n1) {
          return  CompletableFuture.completedFuture(n1)
                    .thenCompose(n -> multiplyBy2(n1))
                    .thenCompose(n -> divideBy3(n))
                    .thenCompose(n -> round(n));
      }
   
      public static void main(String[] args) {        
          CompletableFuture<Double> future = applyOperation(12);
          future.handle((content, ex) -> {
                if (ex == null) {
                    System.out.print("\nOutput = " + content);
                } else {
                    ex.printStackTrace();
                }
                return null;
            });
          System.out.println();        
      }     
  } 

Monads and Context

In the previous examples we use the three monads to provide pipeline, however, each is used to solve problems in a certain context:

  • Optional: it's the null point (NullPointerException) and value of absences in general;
  • CompletableFuture: Used in the context of asynchronous computations;
  • Stream: the basis of declarative programming with collections, providing a cleaner coding style and features such as parallelization and lazy evaluation.

Use Optional gives programs a more defensive and self-explanatory style to formalize the concept of zero return. For example, any method that does not void or early return may or may not return null, depending on your implementation. That is, seeing only his signature is impossible to determine whether the function is "telling the truth" because it could return something other than what we are expecting (null instead of a valid object indicated in the return type).

But what about the non-checked exceptions? Note that again a function/method might not be totally honest, if we stick in your signature because it may return an unchecked exception, and if we do not have access to the source, we have to use try/catch to develop a "safe" software.

In Scala there is the Monad Try, which has the same paper Optional, but applied to the context of the non-checked exceptions (there are no checked exceptions in Scala), as shown in Listing 12.

Listing 12. Using Try Scala

import scala.util.{Failure, Success, Random, Try}
     
  object TryTest extends App {
   
    def sum(a: Int, b: Int): Try[Int] = Try {
      val result = a + b
      if(Random.nextInt(2) % 2 == 0) {
        throw new RuntimeException("Error")
      }
      result
    }
   
    sum(2, 3) match {
      case Success(result) => println("Sum = " + result)
      case Failure(ex) => println(ex.getMessage)
    }
  }

Note that, instead of returning int, the sum function returns a Try[Int], because the function may return an exception, as we indicate by return type.

A Try Monad only has two subclasses, Failure and Success, and use pattern matching to determine which type generated (Success(result:Int) if no exception occurs, and Failure (eg Exception) otherwise).

Github Jason Goodwin (see Links section) there is a version of Monad Try done for Java 8. The implementation uses four files, and Try the principal and the remaining three interfaces only support, as shown in Listing 13 (list only Try.java in the article, removing the JavaDoc to reduce the code block size).

Listing 13. Try Java

 package example10;
   
  import java.util.NoSuchElementException;
  import java.util.Objects;
  import java.util.Optional;
  import java.util.function.Function;
  import java.util.function.Predicate;
   
  public abstract class Try<T> {
   
      protected Try() {
      }
   
      public static <U> Try<U> ofFailable(TrySupplier<U> f) {
          Objects.requireNonNull(f);
          try {
              return Try.successful(f.get());
          } catch (Throwable t) {
              return Try.failure(t);
          }
      }
   
      public abstract <U> Try<U> map(TryMapFunction<? super T, ? extends U> f);
   
      public abstract <U> Try<U> flatMap(TryMapFunction<? super T, Try<U>> f);
   
      public abstract T recover(Function<? super Throwable, T> f);
   
      public abstract Try<T> recoverWith(TryMapFunction<? super Throwable, Try<T>> f);
   
      public abstract T orElse(T value);
   
      public abstract Try<T> orElseTry(TrySupplier<T> f);
   
      public abstract T get() throws Throwable;
   
      public abstract boolean isSuccess();
   
      public abstract <E extends Throwable> Try<T> onSuccess(TryConsumer<T, E> action) throws E;
   
      public abstract <E extends Throwable> Try<T> onFailure(TryConsumer<Throwable, E> action) throws E;
   
      public abstract Try<T> filter(Predicate<T> pred);
   
      public abstract Optional<T> toOptional();
   
      public static <U> Try<U> failure(Throwable e) {
          return new Failure<>(e);
      }
   
      public static <U> Try<U> successful(U x) {
          return new Success<>(x);
      }
  }
   
  class Success<T> extends Try<T> {
   
      private final T value;
   
      public Success(T value) {
          this.value = value;
      }
   
      @Override
      public <U> Try<U> flatMap(TryMapFunction<? super T, Try<U>> f) {
          Objects.requireNonNull(f);
          try {
              return f.apply(value);
          } catch (Throwable t) {
              return Try.failure(t);
          }
      }
   
      @Override
      public T recover(Function<? super Throwable, T> f) {
          Objects.requireNonNull(f);
          return value;
      }
   
      @Override
      public Try<T> recoverWith(TryMapFunction<? super Throwable, Try<T>> f) {
          Objects.requireNonNull(f);
          return this;
      }
   
      @Override
      public T orElse(T value) {
          return this.value;
      }
   
      @Override
      public Try<T> orElseTry(TrySupplier<T> f) {
          Objects.requireNonNull(f);
          return this;
      }
   
      @Override
      public T get() throws Throwable {
          return value;
      }
   
      @Override
      public <U> Try<U> map(TryMapFunction<? super T, ? extends U> f) {
          Objects.requireNonNull(f);
          try {
              return new Success<>(f.apply(value));
          } catch (Throwable t) {
              return Try.failure(t);
          }
      }
   
      @Override
      public boolean isSuccess() {
          return true;
      }
   
      @Override
      public <E extends Throwable> Try<T> onSuccess(TryConsumer<T, E> action) throws E {
          action.accept(value);
          return this;
      }
   
      @Override
      public Try<T> filter(Predicate<T> p) {
          Objects.requireNonNull(p);
          if (p.test(value)) {
              return this;
          } else {
              return Try.failure(new NoSuchElementException("Predicate does not match for " + value));
          }
      }
   
      @Override
      public Optional<T> toOptional() {
          return Optional.ofNullable(value);
      }
   
      @Override
      public <E extends Throwable> Try<T> onFailure(TryConsumer<Throwable, E> action) {
          return this;
      }
  }
   
  class Failure<T> extends Try<T> {
   
      private final Throwable e;
   
      Failure(Throwable e) {
          this.e = e;
      }
   
      @Override
      public <U> Try<U> map(TryMapFunction<? super T, ? extends U> f) {
          Objects.requireNonNull(f);
          return Try.failure(e);
      }
   
      @Override
      public <U> Try<U> flatMap(TryMapFunction<? super T, Try<U>> f) {
          Objects.requireNonNull(f);
          return Try.<U>failure(e);
      }
   
      @Override
      public T recover(Function<? super Throwable, T> f) {
          Objects.requireNonNull(f);
          return f.apply(e);
      }
   
      @Override
      public Try<T> recoverWith(TryMapFunction<? super Throwable, Try<T>> f) {
          Objects.requireNonNull(f);
          try {
              return f.apply(e);
          } catch (Throwable t) {
              return Try.failure(t);
          }
      }
   
      @Override
      public T orElse(T value) {
          return value;
      }
   
      @Override
      public Try<T> orElseTry(TrySupplier<T> f) {
          Objects.requireNonNull(f);
          return Try.ofFailable(f);
      }
   
      @Override
      public T get() throws Throwable {
          throw e;
      }
   
      @Override
      public boolean isSuccess() {
          return false;
      }
   
      @Override
      public <E extends Throwable> Try<T> onSuccess(TryConsumer<T, E> action) {
          return this;
      }
   
      @Override
      public Try<T> filter(Predicate<T> pred) {
          return this;
      }
   
      @Override
      public Optional<T> toOptional() {
          return Optional.empty();
      }
   
      @Override
      public <E extends Throwable> Try<T> onFailure(TryConsumer<Throwable, E> action) throws E {
          action.accept(e);
          return this;
      }
  }

Now let's refactor the example in Listing 3 in terms of this new Monad, as shown in Listing 14.

Listing 14. Using Try.

 package example11;
   
  import example10.Try;
   
  public class Example11 {
      
      public static Try<Double> multiplyBy2(double n) {
          return Try.successful(n * 2);
      }
      
      public static Try<Double> divideBy3(double n) {
          return Try.successful(n/3);    
      }
      
      public static Try<Double> round(double n) {
          return Try.successful(Double.valueOf(Math.round(n)));
      }
      
      public static Try<Double> applyOperation(double n1) {
          return multiplyBy2(n1)
                  .flatMap(n -> divideBy3(n))
                  .flatMap(n -> round(n));
      }
   
      public static void main(String[] args) throws Throwable {                
          System.out.println("Output = " + applyOperation(12).get());
      }    
  }

Note that even using Try this code, the composition of semantics has been preserved (as it was with Optional, Stream and CompletableFuture). Finally, we have the Java version of Listing 12 (Scala), the code in Listing 15.

Listing 15. Java Version

package example12;
   
  import example10.Try;
  import java.util.Random;
   
  public class Example12 {
      
      public static Try<Integer> sum(int a, int b) {
          int result = a + b;
          if(new Random().nextInt(2) % 2 == 0) {
              return Try.failure(new RuntimeException("Error"));
          }
          return Try.successful(result);
      }
      
      public static void main(String[] args) {
      
          Try<Integer> result = sum(2, 3);
          if(result.isSuccess()) {
              result.onSuccess(r -> System.out.println("Sum = " + r));
          } else {
              result.onFailure(ex -> System.out.println(ex.getMessage()));
          }
      }    
  }

Conclusion

We explored in this article some properties of monads and how they help to promote a better viewing functions compositions/methods in Java 8, in addition to providing semantics that help make safer programming day to day.

Despite its mathematical origin (category theory, which is a branch of mathematics that deals with categories/sets and their relationships), the concept of Monad is relatively simple to understand and apply, and therefore, a design pattern (functional) that It will become increasingly familiar and present to Java programmers.

Links

What's Wrong in Java 8: Monads
https://dzone.com/articles/whats-wrong-java-8-part-iv

Jason Goodwin
https://github.com/jasongoodwin/better-java-monads/blob/master/src/main/java/com/jasongoodwin/monads/Try.java



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