Monad Map with two values in Java Functional Programming

52 views Asked by At

Forgive me if this is a basic question in FP. Let's consider Optional monad. I know I can use map to transform an Optional to another Optional based on a function (which will be called if optional has a value). But what if I have a function with two inputs, not one?

For example:

var data = Optional.of(100);
var result = data.map(x -> x * 2); //all good!

But what about:

int getData(Connection conn, int data) { ... }

var data = Optional.of(100);
var connection = getOptionalDataConnection();
var result = data.map(i -> getData(connection?, i));  //this does not work as the getData function only accepts non-optionals

I'm sure there is a term for this in FP world I'm just not familiar with it. But the idea is I have a map function which accepts "two" inputs, rather than one. And I want a helper/utility/... that will call my function if both inputs are there. But if any of them is "empty" should return Optional.empty().

What is this called and does Java support this?

2

There are 2 answers

0
Mark Seemann On BEST ANSWER

What is this called and does Java support this?

This is called a monad, and yes, Java supports it via the flatMap function (monadic bind). Here demonstrated with just two optional integers:

var xo = Optional.of(100);
var yo = Optional.of(2);
var result = xo.flatMap(x -> yo.map(y -> x * y));

In this example, result is an Optional value holding the integer 200.

For the particular example in the OP, you actually don't need a monad, since using the capabilities of the Maybe/Optional applicative functor would be sufficient. I'm not well-versed in the Java ecosystem, however, and I don't see any apply-like methods in the API documentation. You could probably write it yourself, but my experience from Java's cousin language C# tells me that applicative functors are awkward in such languages.

0
rzwitserloot On

Before we delve into this, cripes my spidey senses are tingling. This:

But if any of them is "empty" should return Optional.empty().

Is such a bad idea. It's objectively a pretty big loss in robustness and productivity if you replace 50 NullPointerException errors, which at least point directly at where the problem lies and ensures execution does not continue, with 10 'huh, weird - this code.. it... did nothing for some reason' errors. Those 10 errors can have very nasty side-effects (because code continues), and take way more than 5x as long as those NPEs to fix, so you're in a worse place with more time spent bughunting. And this kind of casual defaulting ('eh, just do nothing!') is exactly how you get to that nasty place.

So, you know - delving perhaps slightly too far into opinion for SO's tastes, but, oof. Don't do any of this. I'm not quite sure it's a great idea in general, but in java this is no good.

NB: Because generics and optional is bad and all that, I've replaced all use of int with Integer because this isn't going to properly work without doing that due to the limitations of primitives and generics.

Partial application

The answer you're looking for is partial application.

What you currently have is a function that accepts a Connection and an Integer and returns an Integer. What you need is a function that accepts an Optional<Connection>, an Integer and returns an Optional<Integer>. Specifically, the Optional<Connection> is provided, but the Integer is an input. So let's make that then:

Optional<Connection> connection = getOptionalDataConnection();
Function<Integer, Optional<Integer>> f = i -> connection.map(c -> Optional.of(getData(c, i)));

Armed with this function, we can do the job easily:

Optional<Connection> connection = getOptionalDataConnection();
Function<Integer, Optional<Integer>> f = i -> connection.map(c -> Optional.of(getData(c, i)));

Optional<Integer> data = Optional.of(100);
var result = data.flatMap(i -> f.apply(i));

It's rather a lot of code and to my sensitivities, the actual essence is mostly lost here. But, it's what's there in java (specifically, Optional's flatMap). You can write utility methods that turn optionals into flatmappable functions.

Let's put it together:

import java.util.function.*;
import java.util.*;

class Example {
  public static <U, V, R> Optional<R> compose(
    Optional<U> first, Optional<V> second,
    BiFunction<U, V, R> func) {

    Function<V, Optional<R>> f = v -> first.map(u -> func.apply(u, v));
    return second.flatMap(v -> f.apply(v));
  }

  public static String make(String a, Integer b) {
    return a + ", " + b;
  }

  public static void main(String[] args) {
    Optional<String> as = Optional.of("Hello");
    Optional<Integer> bs = Optional.of(100);
    Optional<String> an = Optional.empty();
    Optional<Integer> bn = Optional.empty();

    BiFunction<String, Integer, String> func = Example::make;
    System.out.println(compose(as, bs, func));
    System.out.println(compose(as, bn, func));
    System.out.println(compose(an, bs, func));
    System.out.println(compose(an, bn, func));
  }
}

Related terminology that is somewhat relevant to this case is 'currying'. The more general principle of wrapping all sorts of stuff in Optional, I'm not sure that even has a name.