Codementor Events

How learning Haskell helped me to understand flatMap in Java

Published Aug 08, 2019Last updated Aug 27, 2019
How learning Haskell helped me to understand  flatMap in Java

About me

I'm a University student, Programmer graduated and Java Software Developer

Why I wanted to learn Haskell helping to understand flatMap in Java

I wanted to apply Functional Programming in my day to day projects.

How I approached learning Haskell helping to understand flatMap in Java

I started to learn Haskell on university, then with a friend finally by my own and StackOverflow. I knew this concepts will be helpful some day, and could be applied with a simple calculator in Haskell:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Int
eval (Val n) = n
eval (Div x y) = div (eval x) (eval y)

n1 = Val 8
n2 = Val 4
n3 = Val 0 
d1 = Div n1 n2
d2 = Div d1 d1
d3 = Div d2 n3

main = do
  putStrLn $ show (eval d2)

It will print as you can figure, "1". But this example obviusly leads me to the "Zero division" case, and a simple way to emulate it is with the Maybe type:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Maybe Int
eval (Val n) = Just n
eval (Div x y) = do
                  v1 <- eval x
                  v2 <- eval y
                  if v2 == 0 
                  then Nothing
                  else return (div v1 v2)
n1 = Val 8
n2 = Val 4
n3 = Val 0 
d1 = Div n1 n2
d2 = Div d1 d1
d3 = Div d2 n3

main = do
  putStrLn $ show (eval d2)
  putStrLn $ show (eval d3)

Here we go! now we got the output:

Just 1
Nothing

Challenges I faced

Now, how to translate this example into Java code?

First: We create our method into an interface:

import java.util.Optional;
public interface Expr {

    public Optional<Integer> eval();

}

Then we need the cases for Val and Expr, let's go first with the easy one, Val:

import java.util.Optional;

public class Val implements Expr{

    Optional<Integer> value;

    public Val(int value) {
        this.value = Optional.of(value);
    }

    @Override
    public Optional<Integer> eval() {
        return value;
    }
}

Now comes the little twist, how to create the recursive part in java with Optional? Let's take a look:

import java.util.Optional;

public class Div implements Expr {

    Expr expr1;
    Expr expr2;

    public Div(Expr expr1, Expr expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public Optional<Integer> eval() {
        return expr1.eval().flatMap(v1 ->
                expr2.eval().flatMap(v2 ->
                    (v2 == 0) ? Optional.empty() : Optional.of(v1 / v2)
                )
               );
    }

    public static void main(String[] args) {
        Expr iv1 = new Val(6);
        Expr iv2 = new Val(3);
        Expr iv3 = new Val(2);
        Expr iv4 = new Val(0);
        Expr div1 = new Div(iv1, iv2);
        Expr div2 = new Div(div1, iv3);
        Expr div3 = new Div(div2, iv4);

        System.out.println(div2.eval());
        System.out.println(div3.eval());

    }
}

it will print:

Optional[1]
Optional.empty

Here we translate our do notation into two flat maps, the equivalent for ">>=" method, in Haskell the recursive type could be rewritten with >>= like:

eval (Div x y)  = eval x >>= (\v1 -> eval y >>= \v2 -> if v2 == 0 then Nothing else return (div v1 v2))

those two >>= are ours flatMap() from Java.

Key takeaways

it's not so obvious when or where use flatMap, or how to combine it with another flatMap or with a map. The key is to understand if the function will return to you another level in the monad or not.
Another quick example to see this is with this simple example:

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

        Optional<Integer> o1 = Optional.of(1);
        Optional<Integer> o2 = Optional.of(0);

        Optional<Integer> integer1 = o1.map(i -> i + 2);

        Optional<Optional<Optional<Integer>>> integer = o1.map(i -> o2.map(i2 -> div(i, i2)));
        Optional<Optional<Integer>> integer2 = o1.flatMap(i -> o2.map(i2 -> div(i, i2)));
        Optional<Integer> integer3 = o1.flatMap(i -> o2.flatMap(i2 -> div(i, i2)));
        Optional<Integer> integer4 = o1.flatMap(i -> o2.map(i2 -> sum(i, i2)));
        Optional<Optional<Integer>> integer5 = o1.map(i -> o2.map(i2 -> sum(i, i2)));

        List<Integer> l1 = Arrays.asList(5,4,6);
        List<Integer> l2 = Arrays.asList(2,4,5);
        List<Integer> l3 = new ArrayList<>();

        l1.forEach(e -> {
            l2.forEach(e2 -> {
                if(e > e2){
                    l3.add(e + e2);
                }
            });
        });

        List<Integer> collect = l1.stream().flatMap(e ->
                l2.stream().flatMap(e2 -> e > e2 ? Stream.of(e + e2) : Stream.empty()))
                .collect(Collectors.toList());

        List<Integer> collect2 = l1.stream().flatMap(e -> l2.stream().flatMap(e2 -> Stream.of(e + e2))).collect(Collectors.toList());
        List<Integer> collect3 = l1.stream().flatMap(e -> l2.stream().map(e2 -> e + e2)).collect(Collectors.toList());

        System.out.println(collect);
        System.out.println(collect2);
        System.out.println(collect3);
        System.out.println(l3);
    }

    public static Optional<Integer> div(Integer i, Integer i2){
        if(i2 == 0){
            return Optional.empty();
        }else{
            return Optional.of(Math.floorDiv(i, i2));
        }
    }

    public static Integer sum(Integer i, Integer i2){
        return i+i2;
    }
}

With that example, one can play and see how the type fits and the results come out.

Tips and advice

Functional programming is our friend, and could be applied in our day to day work if we can see how. It's fantastic indeed.

Final thoughts and next steps

My next goal is to make another step into flatMap but with Java Streams, I let a quick example. But I want to go deeper.

Discover and read more posts from Damián Rafael Lattenero
get started
post commentsBe the first to share your opinion
Show more replies