3

Okay, so I'm a long time OO developer trying to get into this "newfound" world of functional programming - at the very least, as it pertains to this, I'm trying to code like null and throw don't exist in Java land by playing around with Option and Either monads. (At least, I think they're monads; I'm still not comfortable with that term and whether or not I'm using it correctly...) I'm going off of the Atlassian Fugue library; I tried looking into the Functional Java library, and it's much bigger than I'm ready for at the moment. If fj does what I need and Fugue doesn't, I'm all for it.

Basically, what I'm trying to accomplish is equivalent to this rather ugly Java code:

InputObject input = blah();

try {
    final String message = getMessage(input);

    if (message != null) {
        try {
            final ProcessedMessage processedMessage = processMessage(message);
            if (processedMessage != null) {
                try {
                    final ProcessedDetails details = getDetails(notificationDetails);
                    if (details != null) {
                        try {
                            awesomeStuff(details);
                        } catch (AwesomeStuffException ase) {
                            doSomethingWithAwesomeStuffException(ase);
                        }
                    }
                } catch (GetDetailsException gde) {
                    doSomethingWithGetDetailsException(gde);
                }
            }
        } catch (ProcessMessageException pme) {
            doSomethingWithProcessMessageException(pme);
        }
    }
} catch (GetMessageException gme) {
    doSomethingWithGetMessageException(gme);
}

Naively, using the Either monad, I was expecting to be able to do something like this:

getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
              .fold(this::doSomethingWithProcessMessageException, this::getDetails)
              .fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
              .foldLeft(this::doSomethingWithAwesomeStuffException);

... where each of the logic-ish methods would return an Either containing the appropriate exception (probably a domain-specific error class, but an exception works well enough for now) as the left, and an Option<something> as the right. The doSomethingWith... methods would do whatever logging/processing they wanted to do on the error. An example of the logic-ish methods would be:

Either<GetMessageException, Option<String>> getMessage(final InputObject input) {
  if (input == null) {
    return Either.left(new GetMessageException("Input was null");
  }

  try {
    return Either.right(loadMessage(input));
  } catch (Exception ex) {
    return Either.left(new GetMessageException(ex));
  }
}

The other methods would be defined similarly; preferably not taking an Option for the parameter - the method simply wouldn't get called if the previous method returned Option.none.

Aside from the rats nest of compiler errors from generic types that kept me from actually testing this out, logically it doesn't look like it'd actually do what I want anyways - I'd expected things to essentially no-op after the first Either.left value was returned, but looking at it more I'm guessing it'd keep trying to pass the Either.left result on into the next call and continue that on further.

I was able to kind of accomplish my goal just using Options:

getMessage(input).map(this::processMessage)
              .map(this::getDetails)
              .map(this::awesomeStuff);

Unfortunately, to accomplish the same logic as the original block, the logic methods would be implemented similarly to this:

ProcessedMessage processMessage(final String message) {
  try {
    return doProcessing(message);
  } catch (Exception ex) {
    final GetMessageException gme = new GetMessageException(ex);
    doSomethingWithGetMessageException(gme);
    return null;
  }
}

Since .map lifts the result into an Option, returning null here works fine. Unfortunately, I'm still essentially hiding the error state from the caller (and, even worse IMO, using null to signify an error - and that's saying nothing about whether or not doProcessing returns null for a potentially valid reason).

So, essentially, I'd like to have method signatures along the lines of the earlier getMessage example -

Either<GetMessageException, Option<String>> getMessage(final InputObject input)

I like the idea of being able to look at a return value and know at a glance "This method will either fail or it will return something that might be there and might not." However, I'm too new to FP and have no clue how to go about this.

And my understanding (albeit likely flawed), is that I can do something similar to

getMessage(input).fold(this::doSomethingWithGetMessageException, this::processMessage)
              .fold(this::doSomethingWithProcessMessageException, this::getDetails)
              .fold(this::doSomethingWithGetDetailsException, this::awesomeStuff)
              .foldLeft(this::doSomethingWithAwesomeStuffException);

(although likely with different methods) that will

  1. stop processing at any time after an Either.left is handled (i.e. call the appropriate doSomethingWithXException method and stop)
  2. continue through the whole chain as long as Either.right exists (and isn't Option.none() if the relevant function returns an Option).
  3. only ever call doSomethingWithAwesomeStuffException if the call to awesomeStuff failed - if any other method failed, it won't reach it.

Is what I'm trying to do even reasonable? I mean, I'm sure it's possible some how, but is it too complicated to try and shoehorn this into Java syntax, even using either of those libraries (or others I'm unaware of)?

MCory
  • 437
  • 2
  • 13
  • I think this code is weird at the beginning. You are mixing null-results with exceptions - quite difficult to handle two different approaches. And then if you inverse if-conditions, you could avoid nested blocks. It would make code more readable. Or is it just academic example? – kan May 04 '16 at 21:22
  • 1
    BTW, there is a try-monad https://github.com/jasongoodwin/better-java-monads/blob/master/src/main/java/com/jasongoodwin/monads/Try.java – kan May 04 '16 at 21:29
  • Mostly just academic. I started out trying to write the monadic-style code, and then when that didn't work out very well I did a quick write up of how I'd implement it in "regular" Java. In "real world" Java, I'd likely just have a single catch block that would just log the exception and move on with life. – MCory May 04 '16 at 22:02
  • Oh, forgot - thanks for the link; I might play with that a little to try it out. Downside in the bigger picture is that it's stuck with Throwables - I know I used Exceptions in my examples; that was mainly to make it a little easier to express. It'd be nice to have a solution with non-Throwable domain-specific error objects, hence my use of Either. – MCory May 04 '16 at 22:06

1 Answers1

2

This is for sure an opinionated question. What you want is flatMap also known as the bind operator for monads. Unfortunately not all of the functional libraries do this (ie Guava does not have flatMap on its Optional). RxJava and Reactor does support this type of operation but that requires making your service more stream like (which I highly recommend).

That being said there are lots of problems with doing this kind of logic in Java namely that Java doesn't have any pattern matching or any variants/ADTs/case classes.

Because of the above and because most serializers have issues with Generic containers (ie Jackson) most of the time a good approach is to use behavior free immutable unique classes to return results (usually static inline classes).

I do this all the time in my services. That is create inline static classes that are specific to the a particular service request. You might implement interfaces to reuse algorithmic behavior across the inline classes but the reality is Java has crap support for tuples/variants/ADTs/case classes (see previous).

This is certainly often better (but not always) than throwing an exception where often data is lost.

Adam Gent
  • 47,843
  • 23
  • 153
  • 203
  • I've come across flatMap/bind in my recent travels, and my brain *almost* wants to understand it and the difference between it and map, but it shuts down right before the light goes off. The fugue library's Either class *does* have a flatMap method; it says that it "binds the given function across the right hand side value if there is one"; I went with fold because I'm trying to handle *both* cases (left and right) before moving on to the (possible) next function in the chain, so it looked like flatMap wouldn't work. Am I wrong in thinking that? – MCory May 04 '16 at 22:44
  • Ran out of space, but meant to ask - could you give some kind of example, no matter how contrived, of what you're suggesting with flatMap? Especially chaining with multiple Either-based methods and Option values, if possible. I've definitely gone the route of having container classes that handle response info - failure info and/or data, if present - but was wanting to try the functional/monadic way of doing things for a change. – MCory May 04 '16 at 22:46
  • 1
    It is sort of hard to answer your comment with out a long diatribe that is possibly incorrect but the short of it is that flatMap allows you to *transform* things while being in the *container*. This sounds trivial in Java but when it comes to concurrency and various other execution abstract things this pays off in massive dividends. I just am not smart enough to explain how critical flatMap and friends are (especially after a couple of drinks :) ). – Adam Gent May 04 '16 at 22:47
  • @MCory the reality is the type system in Java mostly sucks but the way to combat it is to make shit load of classes and use the fact that Java does support multi interfaces and inline classes even though it doesn't support variants and tuples. At least Java now has lambda's :). The good news is you are thinking along the right tracks. Java just hasn't caught up yet to the FP functional concurrency revolution (albeit it has made further strides than most languages). – Adam Gent May 04 '16 at 22:51
  • 1
    @MCory let me sober up a little and I'l follow up with some examples. – Adam Gent May 04 '16 at 22:54
  • Looking forward to it :) And for the record, I did a bit of a contrived test on using flatMap - the result was what I'd expected from the docs. It processes it fine if it's a right value, but I lose the error side of things. – MCory May 05 '16 at 00:11
  • Hi Adam, can you please take a look at my post on SOAP webservies - http://stackoverflow.com/questions/36851843/how-to-get-payload-object-from-soap-logical-handler – learner May 07 '16 at 13:50