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
- stop processing at any time after an Either.left is handled (i.e. call the appropriate
doSomethingWithXException
method and stop) - continue through the whole chain as long as Either.right exists (and isn't Option.none() if the relevant function returns an Option).
- only ever call
doSomethingWithAwesomeStuffException
if the call toawesomeStuff
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)?