Using Fugue/FunctionalJava to Move Away From Null and Throws?

1.2k Views Asked by At

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)?

1

There are 1 best solutions below

7
On

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.