functional programming

Functional Design Patterns in Scala: Monoids

Monoids are used to describe an aggregation pattern: whenever we need to combine values of a particular type, a monoid instance helps abstract the mechanics of the aggregation from the program’s business logic. In this post, we will use the LCD Digits kata that we tackled previously as a motivating example for applying this pattern. The goal here is to transform a sequence of input digits into a string resembling their representation on an LCD display. For example, given the input 0123456789, the program should produce:

As before, we will use a case class to represent LCD digits as a product type:

We can then declare each digit 0-9 as an instance of this class in the associated companion object:

Whereas in our previous solution we used higher-order functions to handle the aggregation of digits, in this version we will perform this aggregation with a monoid instance for the LCDDigit type. A monoid is a specialisation of a semigroup, an algebraic structure comprising a set and some associative operation combine, that also has some identity element id for which the following must be true: For all elements s from set S, combine(s, id) == combine(id, s) == s. In practice, these properties enable us to merge elements from a (possibly empty) collection. For convenience, Monoid is provided as a type class in the cats library:

For our LCDDigit type, we construct an instance of  Monoid, specifying in the combine function how two LCDDigit values should be merged, and providing a value for the identity element in the implementation of empty. We will place this monoid instance inside the  LCDDigit companion object so that it gets added to the implicit scope whenever the LCDDigit type is imported.

For the purposes of this example, we will omit the parsing of strings into sequences of LCDDigits since we can reuse the approach from our previous solution, but we will add some syntactic sugar for working with this type by declaring an implicit class within the companion object:

This allows us to format aggregations of LCDDigits as follows:

The way in which LCDDigit elements should be combined is fairly obvious; however, suppose we are working with a different data type in which we are required to support mulitiple strategies for merging values. For example, consider possible aggregations for Company instances (each of which has a name and a market value):

One such aggregation might be to form a new company by concatenating their names and summing their market values (simulating the possible effect of a merger); another might be to select the input company with the highest market value. We can define separate monoidal instances in the Company companion object to represent these two effects:

Because there are now two implicit monoid instances in scope, we need to pass one explicitly when declaring an aggregation:

The benefit of using a monoidal approach here is that the aggregations are encapsulated in the companion object of the domain entity; the business logic only needs to declare that a particular aggregation should be performed, without needing to specify exactly how this should happen. By contrast, this natural separation of concerns would not be achieved if we were to define the aggregation in-line, using a fold:

Monoids are therefore an important pattern for the functional scala developer, and should be considered whenever there is a need to combine values in some way or another.

If you are interested in experimenting further with the examples presented in this post, the snippets below can be used as worksheets in an IDE or with a tool like ammonite:




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.