18

It seems like the Optional in Java 8 is a monad.

Are the Streams also monads?

Can anyone identify the endofunctor and the two natural transformations in the optional monad?

Community
  • 1
  • 1
Ray Tayek
  • 9,841
  • 8
  • 50
  • 90
  • Not knowing Streams very well, I guess it is. Yet, how does it matter? You can't abstract over type constructors in Java, you can't overload on return type either, hence you probably can't write Java code that is polymorphic in the monad constructor. – Ingo Jan 06 '14 at 09:39
  • 1
    It's worth noting that Optional violates the left identity law when handling null values, and is therefore technically not a monad. `Optional.ofNullable(null).flatMap(Optional::of)` yields an empty Optional, while `Optional.of(null)` throws an NPE. Of course, it still offers most of the benefits of monads. – Colin P. Hill Mar 25 '18 at 18:01
  • see [1] that explains that Optional is not a monad. It's also a great introduction to Monads - for java programmers. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html – MichaelMoser Jul 19 '21 at 00:03

3 Answers3

11

EDIT The answer below is incorrect (retained here for history).

Yes, in each case the functor consists of the class and its map method, and the two natural transformations are of and flatMap(identity).

The correct answer seems to be here.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • we would need to define: \displaystyle \begin{array}{lcl} \mathit{return} &::& a \rightarrow \mathit{Stream}\,a \\ \mathit{join} &::& \mathit{Stream}\,(\mathit{Stream}\,a) \rightarrow \mathit{Stream}\,a \end{array} – Ray Tayek Feb 28 '17 at 04:50
  • 2
    The link is nice, but there is no easy way to see if the answer is 'Yes' or 'No'. Please consider updating your question so that people can quickly see what the short answer is. – Titulum Jun 20 '19 at 18:32
  • @Titulum I would like to remove my answer since it is incorrect, but SO won't let me. – n. m. could be an AI Jun 21 '19 at 04:56
  • @n.m. would you be willing to update the question to include a simple 'Yes' or 'No'? – Titulum Jun 21 '19 at 06:29
  • @Titulum No I want to delete the answer. I do not feel qualified enough to provide an answer. If you want to edit it, go ahead. – n. m. could be an AI Jun 21 '19 at 06:45
  • 1
    @n.m., No pressure, I don't really know the answer too. That's why [I asked the question too yesterday, which was flagged as duplicate almost immediately](https://stackoverflow.com/questions/56687166/can-a-stream-pipeline-be-considered-a-monad) – Titulum Jun 21 '19 at 06:51
5

Yes, java.util.stream.Stream satisfies Monad laws.

Following prerequisites are:

  1. Stream should be Functor, i.e. to provide following function fmap :: (a -> b) -> M a -> M b. If we will look into sources, we will see that it already has function Stream<R> map(Function<T, R> mapper)

  2. It should have unit (a.k.a return) operation: unit :: a -> M a. This one is obvious: Stream<T> of(T t).

  3. It should have bind or join operation:

    1. bind :: M a -> (a -> M b) -> M b : Stream<R> flatMap(Function<T, Stream<R>> mapper)
    2. join :: M (M a) -> M a: this one is not present in Stream but we can infer it from bind.

Now we have required functions. But it's not enough yet to call it monad! We need to prove that monad laws are valid. Let's look at them:

Left identity: (return a) bind f <=> f a

        Function<String, Stream<Integer>> f = str -> str.chars().boxed();
        String a = "abc";
        Stream<Integer> left = Stream.of(a).flatMap(f);
        Stream<Integer> right = f.apply(a);
        //left should be same as right

Right identity: m bind unit <=> m

        Stream<String> stream = Stream.of("abc", "def");
        Stream<String> left = stream.flatMap(str -> Stream.of(str));
        // left should be same as Stream.of("abc", "def")

Associativity: (m bind f) bind g <=> m bind (\x -> f x bind g)


        Function<Integer, Stream<String>> f = integer -> Arrays.stream(Integer.toHexString(integer).split(""));
        Function<String, Stream<BigInteger>> g = string -> Stream.of(string).map(str -> new BigInteger(str, 16));

        Stream<Integer> mLeft = Stream.of(47789, 61453);
        Stream<BigInteger> left = mLeft.flatMap(f).flatMap(g);

        Stream<Integer> mRight = Stream.of(47789, 61453);
        Stream<BigInteger> right = mRight.flatMap(integer -> f.apply(integer).flatMap(g));
        //left should be same as right        

So it looks like Streams are really monads! But be wary, same situation might happen as with Optional which sometimes violates Monadic laws. I have a gut feeling that it might be violated in some cases if you will use parallel() because it can change execution order. If you know where it could happen please comment below.

Igor Konoplyanko
  • 9,176
  • 6
  • 57
  • 100
2

If you know Haskell: Java's Stream is nothing else then Haskell's list monad [] and Java's Optional is nothing else the Haskell's Maybe monad.

Werner Thumann
  • 465
  • 4
  • 15