A Monad Menagerie

Jonathan @ Strictly Swift
11 min readAug 2, 2018

--

Chinese calligraphy / openclipart.com

In the last article, I spent some time looking at Monads — data structures which provide a function called bind or flatMap (or >>>=). Monads provide us with a powerful new way to combine functions.

I then spent a bit of time trying to gain an intuition for what a Monad is.

Finally, we saw that Optionals, Arrays and WebData types are all Monads (and defined bind for them).

In this article, I’m going to cover a lot of ground, looking at some examples of Monads that we don’t often see in Swift (or at least, that are not often treated as Monads) : Readers, Writers and Futures.

All of the examples are available on Github. There is a playground to experiment with. Note that to keep the code samples in this article short, I have missed out a few details — you can see working code in Github.

Writer ✍️

Let’s kick off with Writers. A Writer can package up a value together with some information about that value; and as we continue to process the value in subsequent computation steps, we can continue to add information.

It’s often used as a way to log progress, or to keep track of running totals. In many places you’d use a mutating state (e.g. an inout parameter, a write to a global logging function, or even just a print statement) but these are tricky to test and don’t work well in multi-threaded environments.

Writer has two type parameters: the first is the type which is “logged” into or “accumulated”; and the second is the current value of the computation. In other words, we have:

Writer<A:Monoid, V>

V (the computation value) can be any type, but A (the accumulator) has to be a special type called a Monoid. Monoids¹ are just types with an “addition” operator, represented <>, and a “zero” or “empty” value. I’ll not go into Monoids in this article (check the github code for more) -- but note that Array and Int are both Monoids.

The “zero” of Int is of course 0 ; and the “addition” operator is of course +. More interestingly, the “zero” of Array is [] and addition is .append or +.

So, as an example, suppose we are writing a graphics package to manipulate pictures.

struct Picture {
let pixels: [UInt8]
...
}

We want to keep a record of all of the manipulations. To keep track without mutating state, we need to pass the record into each manipulation function, like this:

func makeGreyScale( log:  [String] ) → (Picture,[String] ) {
let greyScaleVersion = self.map { ... }
return (greyScaleVersion, log + ["Convert to grey scale"])
}

This gets very awkward. Imagine we want to apply a sequence of manipulations (say, apply the grey scale, shrink and rotate the picture). Because of the log parameter, we would need to do this in individual steps:

let initialPicture = 
Picture(pixels:Array<UInt8>(repeating:100, count:10))
let greyScaleVersion =
initialPicture.makeGreyScale(log: [])
let resizedVersion =
greyScaleVersion.0.resizePicture(byScale: 0.5, log: greyScaleVersion.1)
let rotatedVersion =
resizedVersion.0.rotatePicture(byAngle: 90, log: resizedVersion.1)
let displayedVersion =
rotatedVersion.0.displayWithLog(log: rotatedVersion.1)
print( displayedVersion.1, displayedVersion.0)

That’s pretty messy. To make this cleaner, let’s define a protocol which represents an operation, and a name that we can use for logging:

protocol PictureOperation {
var name: String { get }
func operation(p: Picture) → Picture
}

Then, we write each operation as a type, instead of as a function:

struct GreyScaleOperation : PictureOperation {
let name = "Convert to grey scale"
func operation(p: Picture) → Picture { /* ... */ }
}

We can now use a Writer monad to adjust the picture and keep track of the picture and the log at the same time:

func adjustPicture(do op: PictureOperation, _ picture Picture) → Writer<[String], Picture> {
return Writer(writing: [op.name],
value: op.operation(p:picture))

}

How has this helped? Well, now we can use >>>= to perform each of these operations and keep track of the log:

let adjusted : Writer<[String], String> =
adjustPicture( do: GreyScaleOperation(), initialPicture ) >>>= { p1 in
adjustPicture( do: RotateOperation(angle: 90, p1) >>>= { p2 in
adjustPicture( do: ResizeOperation(scale: 0.5, p2) >>>= { p3 in
Writer(writing:["Displaying picture"], value: p3.display() )
}}}

Now, adjusted.writing (provided by the Writer monad) contains the log – the “accumulated value” – and adjusted.value contains the final picture.

print(adjusted.writing)
//["Convert to grey scale", "Resize by factor of 0.5", "Rotate picture by angle of 90.0", "Displaying picture"]

This is much more straightforward than trying to keep track of this logging ourselves.

Writer as a Monad

As you may recall, the trick with a Monad is to define the bind function. It looks like this for a Monad M:

struct M<A> {
func bind( f: (A) → M<B> ) → M<B>
}

Here though our Writer has 2 type parameters, A and V. How does bind work? There’s a simple trick: we “fix” one of the type parameters (always the first) and only allow the second to vary in the bind function. HKT supports this via the DualConstructible and FixedType tags (and uses Sourcery magic to take care of the Monoid requirement)

struct Writer<A : Monoid, V>  : DualConstructible {
typealias TypeParameter = V
typealias FixedType = A
let writing: A
let value: V
}

Let’s look at how we define bind for a DualConstructible type. We just include the FixedType in the signature, like this:

func bind<B>(_ m: (TypeParameter) → M<FixedType,B>) → M<FixedType,B>

Given that, I can then define bind for our Writer. What we need to do is apply the function m to the value in the Writer, to get a new value. But we also need to update the writing part as well – for example, to append to the log. How do I do that?

Recall that a Monoid always has an append operator, written as <> (for an Int, append is +, for a String, append is + again, etc). So, because our writing is a Monoid, we can use <> to update it:

func bind<B>(_ m: (TypeParameter) → Writer<FixedType,B>) → Writer<FixedType,B> {
let origWriter = self
let newWriter = m(origWriter)
return Writer<A,B>(
writing: origWriter.writing <> updWriter.writing,
value: newWriter.value )
}

Reader 📚

The Reader monad is used to wrap a function so we can defer its execution until we have the right context for it to operate.

Readers (also known as Func or Function types) are sometimes used to provide dependency injection, where “environment” dependencies like network connections, databases etc. are passed in from the top level of the application and so test and production environments can be swapped easily . However, this sort of global dependency injection can be done in arguably neater ways (see the PointFree episode here); and Readers are more useful for changing contexts within smaller parts of your application, rather than globally.

Readers have two type parameters, and represent a function . In other words:

Reader<X,Y>

represents a function from X to Y . We define Reader like this:

struct Reader<X, Y> : DualConstructible {
let g: (X) → Y
init(g: @escaping (X) → Y) { self.g = g }

func apply(_ x: X) → Y {
return g(x)
}
}

Building bind

Possibly surprisingly, this is actually a Monad, if we “fix” the first type parameter (the X), because we can define bind for it. It’s worth looking what this is like, as it might help explain how the Reader works. (This is a bit tricky: you might want to skip to the Example!)

Remember that for a Monad M and type A, bind looks like:

struct M<A> {
func bind( f: (A) → M<B> ) → M<B>
}

If we say that Reader “fixes” its first type parameter X to become a Monad, I can subsititute in Reader<X,A> for M<A> :

extension Reader<X,A> {
func bind( f: (A) → Reader<X,B> ) → Reader<X,B> { ... }
}

But Reader<X,A> represents a function from X → A, so I can swap that in:

func bind( f: (A) → ( (X) → B ) ) → (X) → B

So; if we have a function from X → A (ie, the original Reader<X,A> we are extending), and another function f which takes an A and returns a function from X → B; then bind gets us a third function from X → B. In all this, X is the context we want to use; and the functions X → A and X → B are the code acting on the context to give us the results.

So, following through all of the function arrows we see how bind is defined:

extension Reader {
func bind<B>(_ f: @escaping (A) → Reader<X,B>) → Reader<X,B> {
return Reader<X,B>{ x in f(self.g(x)).g(x) }
}
}

Example

Returning to our Picture example, the context might be the format we want to output the picture in. Suppose we have a few possible output formats:

enum OutputContext {
case screen(size: CGSize, colour: Bool)
case printout(size: CGSize)
case asciiArt
}

When a picture is rendered, we want to carry out some operations — such as applying a suitable colour tone to the picture, and re-sizing it to fit the output canvas.

extension Picture {
func toneMapping( d: CGFloat) → ???
func scaleToFit() → ???
func performRender() → ???
}

Each of these operations is done in the context of what the output will be. By using a Reader, we can define the list of operations to perform up front, but we don’t need to actually carry them out until we know what the output context is. So, our functions look like:

func toneMapping( d:CGFloat ) → Reader<OutputContext, Picture> {...}
func scaleToFit() → Reader<OutputContext, Picture> {...}
func performRender() → Reader<OutputContext, Void> {...}

The Void on the last line is because the render doesn’t actually return a value, it just renders the picture onto the canvas supplied in the OutputContext.

Then we can use >>>= to glue the functions together:

extension Picture {
func renderToContext() → Reader<OutputContext, Void> {
return .pure(self) >>>= { p1 in
p1.toneMapping(d: 0.5) >>>= { p2 in
p2.scaleToFit() >>>= { p3 in
p3.performRender()
}}}
}
}

Now, when we want to actually do the rendering, we can just apply the right context (apply is provided by the Reader monad):

picture.renderToContext().apply(.asciiArt)
picture.renderToContext().apply(.printOut(size: pageSize))

You might be wondering what the toneMapping, scaleToFit and performRender functions actually look like – how do they return a Reader ? Here’s an example of toneMapping:

func toneMapping(d: CGFloat) → Reader<OutputContext, Picture> {
return Reader { context in
context.mapTone(d: d, picture: self)
}
}

Here, the initialization of Reader is done with a function that takes a OutputContext, and responds with a Picture.

Reader is again defined in HKT as a DualConstructible type.

Future 🔮

Futures are likely to be familiar to many, though you may not be aware that a Future is also a Monad. Futures are in many ways similar to Readers, in that they allow a function execution to be deferred to a later time. However whereas a Reader reads a context and returns a result when the internal state is ready, a Future executes when an external event happens : when data is loaded from a remote server, for example.

The implementation of Future in this article is a HKT-adapted version of the code from John Sundell’s excellent blog post, as that is straightforward and clear to follow. I would definitely recommend reading that as a great introduction to Futures and Promises.

What is a Future?

A set of callbacks are registered with the Future to be called when the external event occurs. The result of the external event has a type Result<Value> :

enum Result<Value> {
case value(Value)
case error(Error)
}

The implementation for a Future therefore looks somewhat like this (See the original blog post):

class Future<Value> : Constructible {
typealias TypeParameter = Value

fileprivate var result: Result<Value>? {
// Observe whenever a result is assigned, and report it
didSet { result.map(report) }
}
private lazy var callbacks = [(Result<Value>) → Void]()

func observe(with callback: @escaping (Result<Value>) → Void) {
callbacks.append(callback)

// If a result has already been set, call the callback directly
result.map(callback)
}
}

Futures are twinned with Promises: a Promise represents a Future where the external event has not yet happened (in other words, the Promise “promises” to return a Value at some time in the future). When the event happens, the promise is either rejected if an error occurred, or resolved if data has been returned successfully. We’ll not show the implementation of Promise here (see the github code or John’s post) but here is an example of how URLSession looks if I add a new extension method request that allows us to return a Future:

extension URLSession {
func request(url: URL) → Future<Data> {
// Start by constructing a Promise, that will later be
// returned as a Future
let promise = Promise<Data>()

// Perform a data task, with the completion handler
dataTask(with: url) { data, _, error in
// Reject or resolve the promise, depending on the result
if let error = error {
promise.reject(with: error)
} else {
promise.resolve(with: data ?? Data())
}
}.resume()
return promise
}
}

In the URLSession code, when the dataTask (an existing method of the Foundation type URLSession) completes, it runs the completion handler and rejects or resolves the promise accordingly. Promise is a subclass of Future.

This gives a nice interface to making a network request:

URLSession.shared.request(url: url).observe { result in
// Handle result
}

Future as a Monad

What if I want to perform two network requests, one after the other? I need to check the result of the first network request; and only if it succeeds, carry out the second request using the result from the first, and check the second result ( finally returning the result of the second request, or an error).

In other words, if I have a Future<A> to carry out the first operation, I need a chaining function to take the result of that and carry out the second operation, (A) → Future<B>, like this:

public func chain<B>(_ m: (A) → Future<B>) → Future<B>

But this is nothing more than our old friend bind once again! With an implementation of this and of pure (see the github code) , we have our Monad definition.

Now, I can carry out my sequential network requests:

func userLoader( id: String, s: LoaderSession ) → Future<User> {
return
s.urlSession.requestData(url:s(id)) >>>= { data in
s.urlSession.requestData(url:s.urlForDetailLookupFrom(data: data)) } >>>• { dt in
User(fromData: dt) }
}

See that >>>• near the end? That’s an operator version of fmap : so v >>>• f is the same as v.fmap(f) . Because all Monads are also Functors, we can define fmap for them – that’s a very useful operation on a Future because it allows us to transform the result of the future from one type into another.

In this case, the >>>• { dt in User(fromData: dt } converts the Future<Data> returned from the Future into a Future<User>.

Conclusion

We have seen some very powerful uses of the Monad pattern, and you may be able to see how you can use them to break your own code up into composable units like Monads (or Functors, or Applicatives). As you can see in the final example, you can also combine Monads, Functors and Applicatives together.

If it has whetted your appetite, you may want to look into other Monads, such as:

  • the State monad
  • the Continuation monad
  • the input/output monad

-…and many others.

If you enjoyed this series, check out my next series, where we use the functional ideas I’ve covered in these articles to build Dyno 🦕 : a real-world library to connect to Amazon’s DynamoDB from Swift! https://medium.com/@JLHLonline/introducing-dyno-ac46e1c4de63

¹I appreciate Monoid and Monad sound very similar — unfortunately they are the “terms of art” and so we are stuck with them!

--

--