What I Learned at Work this Week: map vs flatMap with Either in Java
When I write Java at work, I’m frequently working with vavr’s Either class. And when I’m working with vavr’s Either class, I’m confused 100% of the time. Now, I know what you’re thinking — “how could writing methods where the results could be two distinct types ever be problematic in a language that’s obsessed with knowing every object’s type?” Believe it or not, it has caused problems for me.
One problem I ran into this week is that when a method returns an Either
, we have to parse the return with some sort of map
before we actually have access to the data we’re looking for. Before we dig any deeper, let’s make sure we’re on the same page about Either
.
Either
Here’s some helpful info from the docs:
Either represents a value of two possible types. An Either is either a
Either.Left
or aEither.Right
.Example: A compute() function, which results either in an Integer value (in the case of success) or in an error message of type String (in the case of failure). By convention the success case is Right and the failure is Left.
My Either
methods are usually connected to an API of some sort — if my call to the API fails, I won’t get the object I’m looking for, but an error. So my Either.Left
is always an Exception, and my Either.Right
is the expected/happy path outcome. Here’s a simplified example of something I wrote recently:
Either<Problem, DesiredResult> uploadFile(
String fileName, String fileLocation) {
if (Strings.isEmpty(fileName) || Strings.isEmpty(fileLocation) {
return Either.left(Problem.invalidArgument("Empty argument");
}
return Either.right(s3Service.putObject(bucketName, fileName, fileLocation));
}
I’m asking you to use your imagination here a little bit, as you likely don’t know exactly what the Problem
class looks like, and you haven’t seen me define the s3Service
object. But hopefully the logic here is simple enough to understand the principle: if there is a reason to believe we’re not on our happy path, we’re going to throw a Problem. One other potentially confusing thing here is Either.Left
vs Either.left
. I believe the distinction is that the former is the return’s type or class and the latter is the method we actually use to cast the result as that class.
Handling the results of this method is where we have to use map
or flatMap
.
map and flatMap
The uploadFile
method is connected to logs and a user interface, so we want to propagate whether it executed successfully or not. Here’s a snippet from the method that invokes uploadFile
:
return uploadFile(config.fileName, config.fileLocation)
.flatMap(putObjectResult -> updateStatus(config, putObjectResult);
If we want to access the return value of an Either, we *must* use map
or flatMap
. If we try to access it directly, we’ll get unexpected results. For example, if my Either.right
were a String, I couldn’t do this:
Either<Problem, String> hypotheticalEitherMethod(String testParam) {
if (testParam.equals("badArg") {
return Either.left("This is a bad result.");
}
return Either.right("This is a good result");
// THIS WON'T WORK
return hypotheticalEitherMethod("Happy Path").equals("This is a good result");
Instead, we have to make sure to do this:
return hypotheticalEitherMethod("Happy Path")
.map(result -> result.equals("This is a good result"));
As for the original question in this post, flatMap
gives us even more power when it comes to examining our results. map
's limitation is that it can only parse results on their top layer. So if the result contains another Either
, or an Optional
, or even a Stream
, we’d have to chain on another map
to get in there. A flatMap
, however, will allow us to drill down to the bottom and find what we need. This is most relevant to me when I’m writing Either
methods that call Either
methods. At the very top of the program, I’ll likely need a flatMap
to access what I did at the end of the chain.
I’m still not sure I’d pass an interview question on map
vs flatMap
at this point, but I do have a feeling I’ve been using flatMap
more than I need to in my code. I’m looking forward to doing an experiment and seeing what results I get if I replace some of those with maps
. As usual, I encourage you to read some of the resources I consulted, as they provide more examples and get deeper into the subject matter, so they’ll likely explain things better.