Use the types Luke : Creating a LazyEither from the types and knowledge
Types can guide you (and trick you) when functional programming in Java
Dysfunctional programming in Java 5 : No Exceptions allowed we presented some code that constructed an Either type lazily. It looked like this :-
In this article we’ll break down what is going on in that (⏫⏫) code snippet — we could alternatively expand it out into something like this (⏬⏬)
Let’s look at how we can use the types to figure out how to construct a value declaration like this and what it really means (which in turn means we fully understand the more syntactically sweet version at the very top).
Creating a LazyEither from an eager Either
Let’s start with the method signature of loadContents(), it returns an Either type. That could be a LazyEither (since LazyEither is a sub-class of Either) or a standard eager Either.
Either way (ha!) it is straightforward to get a LazyEither instance from an eager Either, we can call the fromEither factory on LazyEither
LazyEither.fromEither(loadContents());
And it looks like we’re done, the result of this call will match the require type signature perfectly!
LazyEither<IOException,String>
which is also an instance of
Either<IOException,String>
But we’re not done! (😯). Because loadContents returns an Either type, that may have created eagerly. In fact if we look at the code for the implementations of loadContents in Dysfunctional programming in Java 5 : No Exceptions allowed it has been created eagerly!
This means if we simply convert the result of loadContents () to a LazyEither, the method loadContents itself will still be executed eagerly and not lazily! Without changing the implementation of loadContents we want to make the call to loadContents lazy — and we can do this with Eval
If we want caching / memoization so we only loadContents once we can use Eval.later
Eval.later(this::loadContents);
This will give us back a value with the following signature
Eval<Either<IOException,String>> contents;
contents has the following charactertistics
- Is lazily populated on first access ✅
- Is only populated once due to Memoization ✅
- Does not throw Exceptions if the I/O data is unavailable ✅
- Represents the fact the contents data maybe present or absent ✅
- IOException is captured if one is thrown during loading ✅
- 👹Generics hell!👹
What we’d like to do, is condense that down into
LazyEither<IOException,String> contents;
Luckily enough there is a factory method on LazyEither which matches this type in its return signature : fromLazy
We can match the input parameter with a little refactoring of our previous two attempts.
LazyEither<IOException,String> attempt1 = LazyEither.fromEither( loadContents() );Eval<Either<IOException,String>> attempt2 = Eval.later( this::loadContents );
We can create a LazyEither embedded inside an Eval
Eval<LazyEither<IOException,String>> attempt3 = Eval.later( ()-> LazyEither.fromEither( loadContents() ) );
We can use some static imports
import static cyclops.control.Eval.later;
import static cyclops.control.LazyEither.fromLazy;fromLazy(later(()->LazyEither.fromEither(loadContents())));
Which matches the required signature and Laziness semantics of our contents field
So what did we do?
- We converted a lazy, caching Eval instance that loadsContents just once
- We made sure that Eval contained a LazyEither type which either contained the result of loading the contents (a String) or the Exception created when loading contents failed
- Then we constructed a LazyEither instance from that lazy Eval instance — so that externally we have an Either that represents the result of loadContents, but only triggers loadContents once, when absolutely neccessary (on a terminal operation on the Either).
In practice : this is much cleaner
And now you know what it does and how it does it!