SuperKleisliIsFantasticFrameworksAreAtrocious Normally in we use frameworks that resolve dependencies at runtime, and this can often result in hard to track down errors on deployment. A more functional alternative is to structure our programs slightly differently so that dependencies can be injected lazily in a type safe manner. The compiler and not runtime will identify and catch errors for us. Let’s start with a question: Java How can we convert a method’s signature, so that input parameters are moved to the return type? Given the Java method signature below, can we refactor the code such that the DAO becomes part of the return type and not a method parameter? String loadName(DAO dao, long id); public Imagine if our loadName method was implemented as follows String loadName(DAO dao, long id){return dao.loadRecord(id).getName(); public } One solution is to return a function. If we were to return a Function rather than a String, we could make DAO an input to that Function and remove it as a method parameter. Function<DAO,String> loadName(long id); public Now we switch our eager implementation to a lazy one. Function<DAO,String> loadName(long id){return dao -> dao.loadRecord(id).getName();} public To actually load the users name, we need to pass in a DAO instance. String name = loadName(10l).apply(dao); Lazy dependency injection In the example above we have removed the hard dependency on having a DAO when loadName is called. As we shall see in the rest of the article it is possible to chain function calls together, to work with the loaded name and only provide the dependency (DAO) at a point in the code where we have it to hand. Just as we can lazily resolve dependencies injected in method calls, we can use Functions to delay the injection of Objects we may have stored as fields. Converting the Object above to return a Function that injects the FieldType would look very similar to the method level refactoring performed previously. Composing Functions So far we have defined a way where by we can lazily inject a dependency one method call deep. When we call loadName we recieve a function, by executing the Function we can access the name. Photo by on Jakob Owens Unsplash It is possible, but a little more complex to work with the returned name without executing the Function. We can chain operations together on the returned Function. To build a String that contains the users name with the eager version of this method we can write public String loadName(DAO dao, long id); String userData = "User's name is " + (dao,10l); loadName But with the lazy version we will need to work with the Function’s method andThen public Function<DAO,String> loadName(long id); (10l). (name->"User's name is "+ name); loadName andThen Function<DAO,String> sentence = (10l). (name->"User's name is "+ name); loadName andThen System.out.println(sentence.apply(dao)); Where the output type of one Function matches the input type of another, we can chain them together using andThen. Chaining method calls andThen allows us to chain Function calls together, but to chain methods that return Functions we need something a little more powerful. public Function<DAO,String> loadName(long id);public Function<DAO,Boolean> updateName(long id, String name); Using andThen we would end up with a nested data structure (a Function that returns a Function). <DAO, <DAO,Boolean>> fn = loadName(10).andThen(s->updateName(10,+” -suffix”)); Function Function To call our Function we’d need to apply our DAO twice System.out.println(fn.apply(dao).apply(dao)); The Reader Monad: A useful pattern for nested data structures Monads are a very useful pattern in functional for dealing with nested datastructures. This is something they excel at. E.g. We can merge calls to methods that return Optionals inside a chain of operations on Optional and still have a nice unnested single Optional as a result. The flatMap operator flattens the nested Optionals into a single Optional. programming Although we could unwind the Function calls by hand so they are not nested, there is a Monad for Functions -the Reader Monad. <DAO,Boolean> fn = dao->updateName(10,loadName(dao)+" -suffix")); Function The Reader Monad allows us to use flatMap and map (just like for Streams and Optional) instead. To switch from using Function to Reader, just replace Function with Reader as the return type. ( is typically implemented as an extended Function) Reader public < ,String> loadName(long id);public < ,Boolean> updateName(long id, String name); Reader DAO Reader DAO Reader< ,Boolean> r= loadName(10).flatMap(s->updateName(10,s)); DAO When composing Functions directly output types should map to input types, when composing method calls using the Reader Monad it is more important that the input type to the returned Reader is the same. The output type is provided as a parameter during flatMap / map operation. composition of returned Readers all accept DAO For comprehensions Once we start chaining a lot of method calls together nested flatMaps and maps can become a little unwieldy. Stucturing our calls using a for comprehension can help us keep our code a little cleaner. Let’s introduce a new method to log sucesses & failures public boolean logIfFail(long id, String name, boolean success); Chaining a call into this method results in more complex expression Reader<DAO,Boolean> r= loadName(10). (s->updateName(10,s) flatMap .map(success->logIfFail(10,s,success)); We can simplify things somewhat by using a for comprehension We can see the real benefit of for comprehensions when introducing another method call further up the chain, without for comprehensions things can get really messy. public Reader<DAO,Long> findNextId(); Our sequence of calls could become Reader<DAO,Boolean> r= findNextId().flatMap(id->loadName(id).flatMap(s->updateName(10,s) .map(success->logIfFail(10,s,success))); Which looks a lot cleaner when expressed as a for comprehension Awesome, so now we can delay the injection of key dependencies such as DAO’s, have our methods return Reader Monads that will lazily accept our DAO and chain them together using for comprehensions. To execute our chain of functions we can pass the DAO to our Reader and all of the methods we’ve chained together will execute. boolean success = updateName.apply(dao); Asynchronous Readers = complex type signatures! But what happens when we have methods that return other monad types? Reading and writing to a remote store are IO bound operations, perhaps our methods should make use of Futures? Our type signatures are about to get messy! public <DAO, <Long>> findNextId();public <DAO, <String>> loadName(long id);public <DAO, <Boolean>> updateName(long id, String name);public logIfFail(long id, String name, boolean success); Reader Future Reader Future Reader Future Reader<Future< >> Boolean Even for the somewhat simpler use case of chaining together loadName, updateName and logIfFail, our for comprehension becomes horrendously complex! Kleisli to the rescue Kleisli is a function that returns a monadic type. It can represent generically a Function that returns a Future, Stream, Optional or any other monad type in Java. Much like Reader we can define map and flatMap methods for it too. The magic is that although the two types below are equivalent, it is much simpler to work with kleisli than with reader. <DAO,Future<Long>> reader; <future,DAO,Long> kleisli; Reader Kleisli Let’s rework our methods to return Kleisli instead public < ,DAO,Long> findNextId();public < ,DAO,String> loadName(long id);public < ,DAO,Boolean> updateName(long id, String name);public logIfFail(long id, String name, boolean success); Kleisli future Kleisli future Kleisli future Future< > Boolean We can use flatMap and map to chain together method calls using Kleisli, but unlike in our previous examples with just the Reader monad, we now have lazy injection of dependencies and asynchronous execution. < ,DAO,Boolean> k= loadName(10).flatMapK(s->updateName(10,s)); Kleisli future When composing method calls that returned Reader instances we had to keep the input type (the injected dependency) the same. When composing method calls that return Kleisli instances we need to keep the monad type and in the input type the same. Chaining load and save operations In Debashish Gosh’s awesome he shows how to chain asynchronous calls to credit and debit an account inside a single method in Scala using Kleisli. We can use the same technique ourselves in Java. Functional and Reactive Domain Modeling If we define asynchronous load and save methods in terms of Kleisli A processing method can leverage these to load, transform and save data by flatMapping the Kleisli calls. Calling process results in a Kleisli instance which tees up the loading, transforming and saving, but doesn’t execute it. We execute the method chain by calling apply (and passing in a DAO implementation), when we do so our code will execute asynchronously. Much cleaner code Armed with Kleisli we can go back and refactor our code that searches for the next user Id, modifies and updates that users name to use Kleisli for comprehensions (coming in cyclops-react 2.2.0) Many supported Java Monad types Compose All the Things The cyclops integration modules provide higher kinded encodings for , , , , , and even types. Vavr Functional Java RxJava RxJava 2 Reactor JDK /cyclops-react Guava A small sample of the supported types are shown below AnyM based equivalent for working with common monad types, and provides KleisliM which is a Kleisli equivalent when working with monads via AnyM. cyclops-react provides a powerful abstraction AnyM Type safe lazy dependency injection and async execution Kleisli provides a very powerful mechanism for doing type safe dependency injection. Unlike traditional Java dependency injection techniques, we won’t get runtime errors due to unfulfilled dependency relationships (or circularly defined dependencies). What’s more we are able to chain together lazy asynchronous effects without relying on runtime AOP or proxy objects, many more classes of errors will be detected by the compiler rather than the runtime.
Share Your Thoughts