Almost every Java project I’m developing depends on the Vavr library. While I enjoy working with monadic types, sometimes I spot challenging code and wonder how to write it, so it is readable.

One such situation is combining values held by two or more monadic types into a single object. While the task looks very simple, it has some quirks.

Photo by Clément H on Unsplash

THE USE CASE

Consider the following example. We want to manufacture a car which is composed of three parts: engine, wheels, and a body. We can only assemble a car when we have all the elements in place. To pick them up from a depot we need to call three dedicated methods:

As you can guess, if there is a part in a depot, we receive it wrapped in an instance of the Some class; otherwise, it is a None .

LET’S COMBINE

How to consolidate them together, so we have a new shiny car? We can apply monad transformations and do the following:

Yay! It works! But can you read it easily? There is a lot of braces, arrows, and mapping of the stuff around. Maintaining such a creature can be annoying.

ANOTHER WAY

What is the alternative? In Scala, we have a nice solution for such situations. We could apply one of the basic building blocks of this language, which is the for-comprehension construct:

The construct above is more lightweight and is easier to understand, in my opinion. But what about Java? Surprisingly, we can enjoy a similar structure here too! The Vavr library offers its implementation of for-comprehension. When we apply this mechanism to our situation, the code changes to the following:

You may wonder, in what circumstances the construction is worthwhile to apply? I would adopt, when it’s cheap to compute, individual values used in a for-comprehension. The necessity of providing monads/iterables that must be already computed when passing them to the For(...) method is the most important concern in the construct. In our example, it means we have to execute all methods picking up parts from a depot first.

On the other hand, when it is quite expensive to compute values to combine them, e.g. calling an external service or querying a database, maybe using the flatMap method is a better option. In case of any error, while fetching one value, we will not compute the other ones.

Supplying values

A solution to this would be providing yet another variant of the for-comprehension in the Vavr library, accepting java.util.function.Supplier instances. Considering the example, we are talking about, it could look like this:

However, this is not a perfect solution either. The above solves a single case of three suppliers. What about other cases for one, two or even more of them? The next question is about other than Option types returned from suppliers like Try or Iterable . When we consider this, the obvious answer seems to be generating them instead of writing such an amount of code manually. But does the gain of having such variety exceeds the cost of maintaining it? There is no easy answer to this ;)

Combine on request

Another application of Vavr’s for-comprehension could be data permutations. We can do this by using a variant that works on java.lang.Iterable instances. Note that io.vavr.Value extends it so we can use, all Value ’s implementations.

By applying for-comprehension, we can compute combinations of data from multiple sources lazily, because the result is an iterator. It is an excellent way to generate data, for example for testing purposes.

For the above example, I have three remarks. The first one is when calling List.zip/zipWith you end up with a collection that is eagerly computed while for-comprehension provides you with an iterator.

The second remark is about code that is easy to read. Combining more than two Iterable instances by zipping them looks not as clean as the other possibility. It is good enough when you have a list and some other iterable, but for a situation with more collections, I would go with for-comprehension.

And the last, third note is the fact that you have zip/zipWith methods for io.vavr.collection.List implemented. Such situation narrows its usability to specific circumstances. For-comprehension has a more flexible API. We can use it even if we have no List instance in hand.

PROS AND CONS

In my opinion, the key and the first benefit of applying for-comprehension is cleaner code that is not only easier to read, but also to maintain.

It allows even to mix various types since for-comprehension works on java.lang.Iterable . Support of Try/Future/Option types added in v0.10.0 makes it even more useful.

On the cons side or nice to have things, I can list no support for filtering similar to the one in Scala and no versions of the for-comprehension accepting lambdas or suppliers. Without such features, the Vavr’s construct can be sometimes seen as a poor relative to the one in Scala.

WRAP UP

Considering the pros and cons of the construction, we can easily see the limitations of the Java syntax compared to the Scala. While the latter was designed to have such feature out of the box, the former does not even support Try or Either in the standard library.

Next, it is hard to compare features of both for-comprehensions, but Java’s version is nothing to be ashamed of, I think. Having it in your toolbox when using the Vavr library is a good thing and can be extremely helpful in some situations.

Do you have other experiences or thoughts about for-comprehensions? Or maybe do you agree with me? Let me know and leave a comment!