functional programming

Functional Design Patterns in Scala: Monads

Learning (and subsequently trying to explain) monads has become something of a rite of passage in functional programming. Like many developers, I struggled initially to understand precisely what type of thing monads are, despite using them on an almost daily basis. The most helpful description I have come across is given by Noel Welsh and Dave Gurnell in Scala with Cats: “A monad is a mechanism for sequencing computations“. Monads provide a pattern for abstracting over the effects of these computations, allowing them to be composed into larger programs. In this post, we will discuss three such patterns: the Writer, Reader, and State monads.

The Writer Monad

This pattern provides a means to return a log along with the result of a computation. This is especially useful in multithreaded contexts to avoid the log messages of concurrent computations becoming interleaved. An implementation of the Writer monad is provided in cats.data.Writer, which we can use as follows:

We can access the result and the log separately:

A computation wrapped in a Writer monad can be executed with the run method:

The cats.syntax.writer package provides additional syntax for working with Writers, with the pure and tell methods:

The Reader Monad

The Reader monad provides a functional mechanism for implementing dependency injection. It is particularly useful for passing in a known set of parameters into a program composed of pure functions. Another advantage of using Readers is that each step of a program can be easily tested in isolation.

Suppose we have some configuration, whose structure is given by the following case class:

We can then say that programs that depend on this configuration are effectively a function from Config to some type A. We can wrap these functions in the Reader monad, so that they can be composed in the usual way using map and flatMap. In our example, two such ‘programs’ allow us to read a name given in the config object, and perform validation on the configured age:

Because these programs are both expressed using the Reader monad, we can use them as the building blocks of larger programs (which are themselves Readers). We do this in the below example to construct a greeting from the given config:

The program can finally be run by supplying a concrete Config instance to the Reader’s run method.

The State Monad

Programs that carry state along with a computation can be expressed in terms of the State monad. For some state S and result type A, the type State[S, A] represents a function that takes an initial state and returns a result together with some new state. In the example below, we will show how a sequence of arithmetic operations can be chained together, passing the result as the state between each step:

Because each of these individual steps is wrapped in a monad, we can chain them together using a for-comprehension:

The resulting program can then be executed using the run method, passing in an initial state:

We can use the runA and runS methods to return only the result or state respectively:

Summary

We have seen from the examples in this post that the general concept of a Monad allows us to build different types of sequential programs. The implementations of flatMap and pure encapsulated in the implementations of Writer, Reader and State handle the mechanics of this composition, allowing us to focus on the business logic within each step of the program. In this sense, we can see Monads as an extremely general pattern for building the more specialised tools that we have examined here.

Discussion

No comments yet.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Email Updates

Subscribe to this blog and receive notifications of new posts by email.

Twitter


Read this blog in your favourite news reader:
Subscribe