A type safe, JAVA Monad API. Part I : With Vavr’s Future and Try

John McClean
6 min readJul 24, 2017

--

An example use case for higher-kinded-types in Scala is posited in Sam Halliday’s rather awesome looking “Functional Programming in Scala for Mortals with Cats”. And that is to simplify testing asynchronous code.

If we make our application asynchronous, we may well return a Future type from each of our methods e.g. the Work interface below returns Futures indicating asynchronous execution.

The fact that these methods execute asynchronously does complicate writing unit tests for them. It would be nice if we could change the return type depending on the execution context.

In this article we will show how we can write code that can cleanly and safely switch between using the synchronous Vavr Try Monad and it’s asynchronous Vavr Future cousin. We will do this using the AnyM Monad API from cyclops. We will return to our problem later in the article, first we need to explain how to use the latest version of the cyclops Monad API.

Type safe Monad APIs in cyclops-react

Just over 2 years ago, we presented the first draft of our Java API for Monads (see https://medium.com/@johnmcclean/introducing-the-cyclops-monad-api-a7a6b7967f4d). v2 of cyclops-react introduces a much more powerful and typesafe version of this API. In fact in cyclops-react 2 there are two ways of interacting with Monad types, in a fully type safe manner.

  1. AnyM monad API
  2. The Monad typeclass

This article will show how to interact with any Monad type via the AnyM Monad API. The AnyM Monad API follows a more pragmatic approach to interacting with Monads, maximizing the expressive power of the API while the Monad typeclass tries to follow a more purely functional and is much more constrained in what it will allow. Future articles will explain in depth how to use cyclops Typeclasses effectively. This article will show how to make use of the AnyM monad API.

What is the AnyM monad API?

The AnyM Monad API consists of 3 interfaces. AnyM which represents AnyMonad and 2 sub-interfaces AnyMValue and AnyMSeq. AnyMValue represents a Monad type that can manipulate or return a single value while AnyMSeq represents a Monad type that can manipulate a sequence of values. Monads that don’t quite fall into these categories can be manipulated via the AnyM monad API, but are probably much better candidates for manipulation via a Monad Typeclass instance for that Type.

AnyM / AnyMValue / AnyMSeq provide a large range of useful and type appropriate operators, that as far as possible will respect the nature of the underlying Monad. For example manipulating a cyclops-react Maybe via AnyMValue will respect the lazy / reactive nature of the type, while manipulating a JDK Optional or Vavr Option via the same API will result in eager operations. Likewise functional operations teed up on an RxJava Observable via AnyMSeq will executed reactively (asynchronously as the data arrives).

Why a new version was neccessary

The initial cyclops Monad API suffered from a number of drawbacks, chief among them was that a compromise was made that dropped the type information for the monad being used. i.e.

The latest version of the cyclops Monad API solves this problem, by using the technique expounded in Simulating Higher Kinded Types in Java to retain the type information about the Monad being manipulated.

The Witness class in cyclops-react contains useful static methods for converting JDK and cyclops-react types back to their native form.

In the cyclops integration modules equivalent Witness classes are provided per module.

Manipulating Monads

Once we have created an AnyM instance

We can perform typical functional & monadic operations on it.

We can use a more specific AnyM Instance (in this case AnyMSeq will give us more power)

And now we can perform more advanced Sequencing operations on our Stream than are present in the JDK API!

But the nice thing is, we can manipulate a List, Stream, Vavr Vector, RxJava Observable all in the same manner.

More generic code

If your clients want to provide data to you in different formats (a single Optional value, an asynchronously arriving Future data, a large concrete list of values, or a lazily generated and perhaps infinite Stream) you don’t have incur the conversion costs of converting between an internal type and a range of external types. AnyM allows you to define a set of operations in a generic fashion that can manipulate the data provided by client code and return it back to them in the same format.

We can write code that accepts and returns AnyM instances with a generically defined Monad type (in other words, type safe methods that work with any monad type).

Defining a generic method using AnyMSeq

Vavr Future and Try : Abstracting over execution style

The functional library Vavr provides a Future implementation that we could use, and also an analogous synchronous type Try. (cyclops-react also provides both a Future and Try type, however the Try in cyclops-react is not analogous to a synchronous Future). Vavr Future’s and Try’s are analogous in that both ultimately resolve to a value (Future complete, or Try success) or an Exception type. Both simplify the user interface by hiding the exception type, and will catch any Non-Fatal Throwable implementations. Ongoing map / flatMap and other functional operations that throw an Exception result in the Future or Try catching that Exception and finishing in a failed state.

We can make the Work interface more Generic with AnyM, and easily switch between synchronous and asynchronous implementations that return a different monadic type.

Let’s do some incredibly important work!

With our GenericWork interface defined we could write some code that uses to do something useful, but in the interests of keeping things simple we will use it to capitalize Strings. God’s work indeed. (Alright, alright, I was completely out of ideas.. must do better next time..).

We can define two different implementations of GenericWork making use of the cyclops-vavr integration library.

AsyncWork (will use Future)

SyncWork (will use Try)

Asynchronous Work

We can define an Async Version of this class that uses Vavr Futures for Async exeuction

Synchronous Work

We can define a Sync implementation using Try

Switching between Asynchronous and Synchronous execution

We can write some test code, to test both our Synchronous and Async implementations. In the first code block we run the Capitalizer asynchronously and in the second synchronously.

The output will look something like this

The next installment

In this article we covered how we can use AnyM and cyclops-vavr to abstract over asynchronous execution without having to duplicate code or make changes to our monadic return types. In future installments in this series will cover other aspects of the AnyM api in cyclops-react including AnyM function set for using Monads in functions ,KleisliM / CokleisliM types for composing monadic functions, a range of AnyM based Monad Transfomers and XorM which behaves as an Either type for Monads. Separately we will start another set of blog entries on how to leverage the more constrained suite of higher-kinded type classes.

Further reading

cyclops java : The Monad Typeclass with Vavr’s Future and Try

cyclops java : The Active typeclass manager

Simulating Higher Kinded Types in Java

--

--

John McClean

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie