Advanced FP for the Enterprise Bee: State

Garth Gilmour
Google Developer Experts
6 min readFeb 19, 2021
Bees Collaborating to Build Honeycomb

Introduction

Welcome to the penultimate article in our eight part series, examining advanced functional programming for the practically minded enterprise developer. This article will expand on the use case from the previous article, where we developed a set of immutable types to configure an instance of JetBrains Space. On this occasion we are exploring the world of Monads, and in particular the State type. As always the sample code is available in this repo.

The House Of ‘M’

Just like in the comics, the House Of M is a scary place in software engineering. It’s a disconcerting alternate reality - where alien types with strange names and mysterious abilities seem to have taken control. Fortunately the reality is much more banal.

In order to write software we need to represent things in code. Customers, orders, deliveries, cars, livestock etc… But, because programming is a complicated profession, we cannot always use these types directly. Instead we need to wrap them in various kinds of container, based on what complications we anticipate.

Some of these containers qualify as Monads. The simplest is the Identity (or Id) type, which wraps a value without adding any functionality. For example here’s the Identity Monad of Grogu:

We can make the concept of Monads more concrete with a few real world examples. Let’s say we have a Customer ID, and need to fetch matching orders from a database:

  • If the Customer might not have orders we use an Option<Order>
  • If there might be many orders we use a collection, such as List<Order>
  • To manage errors we could use a Try<Order> or Either<Error, Order>
  • In the case where, for some orders, only the total value was available we could use an Either<Double, Order>
  • If the order was subject to a series of checks, any of which could fail, we might use a Validated<List<String>, Order>
  • To postpone querying the database, we could wrap our query code in an Eval or an IO. We might do this in order to batch queries for performance.

What makes these containers Monads is that they are generic, can be combined according to a set of rules and implement certain functions (such as flatMap). Most usefully, for the working developer, functions that return Monads can be conveniently combined via a uniform procedure.

To better understand how this works, we will apply the State Monad to our existing codebase for configuring instances of JetBrains Space. State is one of the more abstract and esoteric Monads, so if we can make sense of it, then the rest should pose little difficulty.

Introducing the State Monad

The State type is used with functions which:

  • Require some information (the state).
  • Return both a modified version of the state and a result.

If S represents the type of the state, and R the result, then the signature of the function is S => (S, R). Typically we want to compose multiple functions, where every invocation requires the state returned by the previous one.

In our case study from the previous article, we generated output using some rough and ready toString methods. Let’s replace this with code using the State type. The state used by our functions will be an Instance object, which models the projects, profiles, repositories and blogs within an instance of JetBrains Space. The return value will be a List<String>, which holds lines of text that we want to print.

The code below shows a displayInstance method, using the State type. We are combining calls to displayTitle, displayProfiles, displayProjects and displayBlogs. Each of these is responsible for processing one section of an Instance. As discussed they take an Instance and return a list of strings:

The functional composition within the fx block takes care of passing the state into each function. So all we need to do is capture the results into local variables. At the end of the block we combine and flatten all the return values into a single list. This is then output via foreach and println.

Note that we need to call runA to trigger the block, and pass the initial state as a parameter. At the moment we need to wrap up the result in an Id monad and call fix to perform a downcast. Neither of these will be required when the State type becomes an official part of Arrow.

We can invoke displayInstance in the usual way, and the output is a complete description of our Space configuration:

Kotlin Programming for MegaCorp
Profiles are:
Pete Smith at Pete.Smith@megacorp.com
Jane Jones at Jane.Jones@megacorp.com
Projects are:
Project 'Course Exercises' (PROJ202) with repos:
http://elsewhere.com
Project 'Course Examples' (PROJ101) with repos:
http://somewhere.com
Blogs are:
Setting Up
setup.md
More client-specific content
Welcome to the Course
welcome.md
Some additional client-specific content

Examining the Display Functions

Here’s the simplest of the display functions:

As you can see we are using the ‘functions as values’ style, where an immutable variable refers to a Lambda. We use the State type to specify the inputs and outputs. Here the input is the Instance, whilst the output is a single item list, combined with the Instance via toT.

The displayProfiles function is slightly more complex. For every profile in the instance we need to generate a string containing the users name and email. This can be achieved by a call to map. We use the cons Optic to prepend a title string and then combine it with the Instance via toT.

The final two functions have the same structure, but we need to handle nested content. In the case of displayProjects it is repositories. In the case of displayBlogs it is additional content:

Using State to Build the Instance

The previous example showed using the State type, plus monadic composition, to output a description of our Instance. However we were not modifying the state at any point.

To show that this can be done let’s rewrite our createInstance method to use the state type:

Once again the state will be an Instance object. But we will incrementally add to it as the function calls proceed. Remember this type is immutable, so every mutation returns a new instance.

For our result type we will use a simple String, which describes what has been achieved. Once all the methods have been called we combine the results and print them before the call to displayInstance:

Created Projects Created Profiles Created Blogs
Kotlin Programming for MegaCorp
Profiles are:
Pete Smith at Pete.Smith@megacorp.com
Jane Jones at Jane.Jones@megacorp.com
Projects are:
Project 'Course Exercises' (PROJ202) with repos:
http://elsewhere.com
Project 'Course Examples' (PROJ101) with repos:
http://somewhere.com
Blogs are:
Setting Up
setup.md
More client-specific content
Welcome to the Course
welcome.md
Some additional client-specific content

Here’s the declaration of copyProfiles. Note that this is a higher order function. We pass in the DslInstance and get back a generated function that takes an Instance and returns a State<Instance, String>. The toT helper method is used to combine the modified Instance and the String report. The new Instance will be passed on to the next function, whilst the result is stored in the log1 local variable.

The copyProjects and copyBlogs method are similar, but more complex because they need to create nested content:

Conclusions

We have been using Monads all the way through this series of articles, but without making special mention of it. A Monad is a container that wraps around one or more values to provide extra functionality. Monadic types like Either, Validated and State are provided by functional libraries like Arrow (Kotlin) and Cats (Scala). Although every Monad is a Container not every Container is a Monad. For example Future is not Monadic because it is non-deterministic.

One of the neatest things about Monads is the ability to chain function calls, with the mechanics of the composition handled for you. In the case of the State type each call returns a pair of a result and a new version of the state. We want to pass the state into the next call and store the result for later use. At the end of the chain we can pass on the results, the final state or both.

Thanks

Continuing gratitude to Richard Gibson and the Instil training team for reviews, comments and encouragement on this series of articles.

--

--

Garth Gilmour
Google Developer Experts

Helping devs develop software. Coding for 30 years, teaching for 20. Google Developer Expert. Developer Advocate at JetBrains. Also martial arts and philosophy.