Monads can be a great addition to your programming toolbox: they provide an abstraction and a design pattern that help writing more composable and declarative code. However, the path to Monad Enlightenment is either paved with “learn Haskell” or “it’s just flatMap.” This talk from SLUG will help build an intuition for what Monads are in the context of Functional Programming, and also respond why and if you should care about them.
Introduction (0:00)
My name is Raheel Ahmed, and I work at PlanGrid. We make software for construction people, Apple, along with other mobile and web clients. The goal of this post is to introduce monads via Swift syntax and to relate some common use cases. It’s more food for thought rather than a reference point, and hopefully, it will encourage you to learn more about this.
It’s not a tough topic, but what it represents is different than what you usually encounter, especially in object oriented programming. It’s not a design pattern. It’s not any of your classes or any other kind of encapsulation idea. It’s something unique to itself.
Fundamentals of functional programming (2:19)
First of all, I wanted to cover basic fundamentals of functional programming. Monads make a lot more sense in functional programming rather than object-oriented even though you can use the same concepts. Let’s start with this:
[Functor, Monad].flatMap {
Definition, Example, Intuition
}
That is not valid syntax. A Functor
is the first step towards monads. For both functor and monads, we will look at some definition and examples. The purpose of this talk is to think: “Do these topics even matter for me on a daily basis?”
So, what are the fundamentals of functional programming, or FP?
- Immutable values, use
let
s and value types. - Pure functions, the same output for the same input always.
Those two things are essentially about reducing the possibilities range in your program. It’s easier to reason about correctness and making it more deterministic.
Then:
- Higher functions.
- Declarative programming.
They’re more about writing in a style that’s clear.
Eventually, your code will be less error-prone. Here’s a small example:
let someDate: NSDate?
let label: UILabel
// label.text = formatted date
You have a label and an optional date, NSDate
. You want to format the date so that you can plug it into the text
property of the label
. Go ahead and write your canonical formatting function that formats it based on your UI needs:
let someDate: NSDate?
let label: UILabel
func formatDate(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.dateStyle = .ShortStyle
formatter.timeStyle = .ShortStyle
return formatter.stringFromDate(date)
}
Now, how do you plug it in? How do you actually make use of this function?
let someDate: NSDate?
let label: UILabel
func formatDate(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.dateStyle = .ShortStyle
formatter.timeStyle = .ShortStyle
return formatter.stringFromDate(date)
}
// label.text = formatDate(someDate) ╳
You can’t do that. You can’t pass your optional NSDate
to formatDate
because formatDate
takes a non-optional NSDate
. This is what you would usually do:
if let date = someDate {
label.text = formatDate(date)
}
You would use if let
, the Swift sugar around unwrapping the value inside the optional (if there is one, it will be in the date
variable). You unwrap it and should pass that to formatDate
, and the value that you get out of that, you can apply. There are two ways in which you can improve on this:
- It’s imperative (slide X). You’re actually writing exactly what you want to do, step by step
- You’re also not handling the
nil
case. What ifsomeDate
isnil
? You actually should have Ls where you saylabel.text
equals nil. You would write something like this:
label.text = someDate.map { date in
let formatter = NSDateFormatter()
formatter.dateStyle = .ShortStyle
formatter.timeStyle = .ShortStyle
return formatter.stringFromDate(date)
}
You make use of the map
function, which is a higher order function. You’re assuming that the formatDate
function isn’t there. But this is what we often end up writing; we write a closure in which we do the transformation. Again, it’s pretty imperative.
That’s the transformation that should happen line by line. Go ahead and write something like this to have a name for that transformation:
label.text = someDate.map {
formatDate($0)
}
You have factored away from the implementation of the closure, but essentially it’s still imperative. You’re still calling formatDate
with the argument in this case, $0
represents a real NSDate
, not an optional. You should end up writing something like this:
label.text = someDate.map(formatDate)
That is more concretely an instruction at a higher level of abstraction. You’re not calling formatDate
here. You’re passing formatDate
the function to map
. You’re letting map
do the work of doing the transformation if it needs to be done if there is a value in someDate
. If there isn’t, then, this whole thing will just return nil without doing any transformation, or you can say that that’s a transformation.
Functors (8:37)
What’s a monad? First I want to talk about functors, which is the stepping stone to monads.
The basic intuition is that you have some sort of container, or you can call it “context.” You can put a value inside that context. Probably when I am saying this, you are thinking about generics already. In most cases, in all of these topics, generics keep on coming. You put a value inside a context, but why do you do that? Because a context provides some functionality that you want.
The second intuition around this is that the functor provides an interface through which you can transform or map the contained value that you put in the context. That’s really it. These are some examples of functors:
struct Array<Element> { }
enum Optional<Wrapped> { }
enum Result<T, ErrorType> { }
T -> U
You have an Array
, Optional
, Result
, and a function is also a functor, so that’s there as well. But what is the functionality that these contexts individually provide?
- An
Array
provides multiplicity that you can have multiples of a value of that same type. Optional
provides optionality. Either the value’s there, or it’s not there.Result
type provides optionality again, like presence, but also in the case of absence, there is an error. These are reasons why you would want to use these contexts.
And then a function. What kind of functionality or what kind of context is a function? Well, you could say that it stores a value, in this case, the values of type A. When you call it at any time you can get that value back again, that’s really it.
So these are the functors:
extension Array {
func map<U>(transform: Element -> U) -> [U]
}
enum Optional<Wrapped> {
func map<U>(transform: Wrapped -> U) -> Optional<U>
}
enum Result<Value, ErrorType> {
func map<U>(transform: Value -> U) -> Result<U, ErrorType>
}
The second point is that they allow mapping. The value stored inside the functor. The functor provides an interface, and that’s the map. They all provide map
. This interface allows you to then transform the value that is there. All of the transform functions have the same signature. They take those functions and transform them. They take a value of the type that’s already there in the functor, and they’ll give the functor back again but with the transformed value. You can see how intuitively how they will be implemented by the individual functors.
Here is an example of map
for function:
func map<T, U, V>(f: T -> U, _ transform: U -> V) -> (T -> V) {
return { t in
transform(f(t))
}
}
I’ve shown the implementation in this case because it’s just so small. Map for a function is essentially function composition. What are we doing here? We have a function f
, and we want to map that function f
with again, a transform function.
What will we get back? We’ll get back a functor
, in this case, a function. But the types are now different. Now the type is V
instead of U
. A transform has happened. How has the transform happened?
Here are more examples:
func formatDate(date: NSDate) -> String
This is the formatDate
we saw earlier, let’s revisit it. We have written this formatting function, with great care, and it should be used everywhere in our app for all formatting needs.
Now we are writing NSDate
, the optional, as really what it is without the sugar that Swift provides:
1. let someDate: Optional<NSDate> // NSDate?
2. someDate.map(formatDate) // Optional<String>
It’s an enum. It’s generic with the type inside. Then you can say, in the same code that we saw in the beginning, “someDate
, map that functor, using this function.” That’s pretty declarative, straightforward. The type inside the functor may change, in this case, it has changed from a date to a string, but the functor itself remains. Optional
is still there.
Our next example:
1. let dates = [ yesterday, today, tomorrow ] // [NSDate]
2. dates.map(formatDate) // [String]
The same function, formatDate
is again being used here. Then the same syntax, in the second line. In the first line you have an array of NSDate
s, and the second line reads exactly like the previous. You are transforming an array using the same syntax and the same function. You’re getting the same context array back but with the values transformed by the same function.
Our next example, Result
, it’s pretty much the same thing as an optional:
1. let resultingDate: Result<NSDate, NSError>
2. resultingDate.map(formatDate) // Result<String, NSError>
Again, the syntax and the semantics also carry over from functor to functor. Function
.
let dateAtSection: Int -> Date
map(dateAtSection, formatDate) // Int -> String
We have a function dateAtSection
, which takes an index of a section as an int
, and returns the date at that index. That may be for a table we use section, the title, maybe. But then if you have the formatDate
function again you can map your function using that implementation that we saw a little while ago. You can map
your function using that formatDate
function so that now that function becomes something that takes an Int
and returns a String
.
That is what a map does, it lifts a function that works on basic, simple types. It takes a function like formatDate
, and lifts it up so that it can work in functors. The transformation earlier was happening in just primitive types, and now it’s happening in functors.
Just one more thing about functors:
dateLabel.text = someDate.map(formatDate)
// vs.
if let date = someDate {
dateLabel.text = formatDate(date)
}
You want to stay in the functor as much as possible. In the second case, you probably don’t want to do this because you would have to do the Ls and then make sure that the label or text equals nil
also is handled. If you don’t have to unwrap it, don’t unwrap it. Just use map, or later as we will see, use flat map.
It is almost always better to stay in the functor. Only extract the value if you absolutely have to. Let map
internally do the transformation.
This is the building intuition part of the functors. Functors contain transformations, so the map function that each functor defines, it contains the transformation inside so that you don’t have to write that when you are writing your regular code. Otherwise like in the case of Optional
, you would have to do a switch.
Swift has an if let
, so that’s easier, but in the case of result type, for example, you would have to do a switch on the result. If there is a value then actually do the transformation. The map lets you extract that. You always want that. You’re not writing the map code everywhere like we used to do in Objective-C. We didn’t have a map in the standard library. It gets more complex for more complex functors.
The second intuition part is that it allows you to compose transformations
label.text = someDate
.map(formatDate)
.map(upper)
You can see an example like this, if you’ve got a few transforming functions, like formatDate
, and an uppercase transformer, string to string. You can chain them together, and it’s declarative code. You know what is going on because you understand the map.
You would want your transforming functions to be pure functions almost always. You expect these formatDate
and upper
to do their transformation and not fire off a network request or something. If you want to re-order these you don’t want to think about the order of effects and so on.
If you’re writing another data structure like a tree or something, anything that contains a value, in most cases, it’s very easy to then think of the map for that. It’s very easy to make it a functor. But also for custom structures.
Say if you have a cache, a cache of a certain type like maybe a cache of users. You can possibly see that cache wholesale being mapped to a cache of images of users. That implementation would probably be more complex, and you’d want to do lazy transformation rather than eager. You can easily see yourself buying into this for your own code or your custom classes and structs.
Monads (20:26)
We’ll do the same thing for monads. The basic intuition is, again, surround a value inside a context. The second thing is, the monad gives you an interface to allow the mapping and then flattening of the contained value. Mapping is easy because we use that term in regular language as well, but flattening is something that’s somewhat abstract as to what it really means for different monads. We’ll look at some examples:
struct Array<Element> { }
enum Optional<Wrapped> { }
struct Result<T, ErrorType> { }
class Signal<Event, ErrorType> { }
Besides the function, everything else that we looked at earlier that was a functor is still a monad. I’ve added a signal type that you may be familiar with if you have done any reactive programming. Or promises, same idea.
This is the flatMap
:
extension Array {
func flatMap<U>(transform: Element -> [U]) -> [U]
}
enum Optional<Wrapped> {
func flatMap<U>(transform: (Wrapped) -> U?) -> U?
}
struct Result<Value, E> {
func flatMap<U>(transform: Value -> Result<U, E>) -> Result<U, E>
}
extension Signal {
func flatMap<U>(transform: Value -> Signal<U, Error>) -> Signal<U, Error>
}
If you compare this with map
, it’s almost the same.
extension Array {
func map<U>(transform: Element -> U) -> [U]
}
enum Optional<Wrapped> {
func map<U>(transform: Wrapped -> U) -> Optional<U>
}
struct Result<Value, ErrorType> {
func map<U>(transform: Value -> U) -> Result<U, ErrorType>
}
extension Signal {
func map<U>(transform: Value -> U) -> Signal<U, Error>
}
You can see that the transform
function has a different signature. The transform
function itself now returns a monad. The thing on which you’re calling or the thing on which map is defined, that thing is being returned from the transform
functions. Now it’s more aware. You’re thinking about the monad a little more and less about the transform
function because the monad is reflected all over the place. We’ll look at some examples.
If you have a table view with cells, and each cell represents an issue that you want to show, and each issue has some users, some of those users may be collaborators, like special users, and some are general users. Here is a simple data structure for both of them:
struct User {
}
struct Issue {
let users: [User]
}
And then you have some functions:
struct User {
}
struct Issue {
let users: [User]
}
func issue(index: Int) -> Issue
func collaborators(issue: Issue) -> [User]
You have a function that takes an index of a cell, for example, and returns the issue at that index, like a data sourcing function. Then you have another function that takes an issue and then figures out what are the actual collaborators for that issue and returns you that. And that returns an array. So now you have an array of indexes of selected rows:
struct User {
...
}
struct Issue {
let users: [User]
}
func issue(index: Int) -> Issue
func collaborators(issue: Issue) -> [User]
let selectedRows: [Int]
// selected collaborators
What you want are the selected collaborators, not the issues:
struct User {
...
}
struct Issue {
let users: [User]
}
func issue(index: Int) -> Issue
func collaborators(issue: Issue) -> [User]
let selectedRows: [Int]
// selected collaborators
selectedRows
.map(issue) // [Issue]
.map(collaborators) // [ [User] ]
You map selectedRows
using the issue
function. Then you map again using collaborators
. collaborators
now is going to take each issue
and then return an array for each one of them, so what you get is an array of arrays of users. That’s not what you want, you want that to be flattened. Here is what you would get if you use flatMap
:
struct User {
...
}
struct Issue {
let users: [User]
}
func issue(index: Int) -> Issue
func collaborators(issue: Issue) -> [User]
let selectedRows: [Int]
// selected collaborators
selectedRows
.map(issue) // [Issue]
.flatMap(collaborators) // [User]
flatMap
flattens out that result. It’s going to apply the collaborators
function on each of the issues from that second to the last line, and then internally it’s going to then flatten it all out before returning you the result. Unlike map
which returns things as they were mapped.
If you put this in the playground and play with it, you’ll see the types. The nice thing about working in Swift for this stuff rather than dynamic language is that you can see the types and if you play around with it everything matches.
Another short example:
dateStringsForSection: Int -> [String]
durationStringsForSection: Int -> [String]
[dateStringsForSection, durationStringsForSection]
.flatMap { $0(section) }
We have an array of functions, and then apply flatMap
on them.
Here is another example:
func userForName(name: String) -> User?
func avatarForUser(user: User) -> UIImage?
let myName = "Raheel"
// Compute the image for the given name
You have a function that does a lookup for a name and returns an optional user. Maybe it does some caching or some database lookup, it may fail, maybe there isn’t a user with that name. And then the second one takes a user and returns an image, maybe that’s looking in a cache for that user’s avatar.
What you want is to get the avatar given a name. Here is the final solution:
func userForName(name: String) -> User?
func avatarForUser(user: User) -> UIImage?
let myName = "Raheel"
userForName(myName).flatMap(avatarForUser)
If you used map
on the first optional, what you would get out of the map application at the end would be an optional of optionals. Just like in the previous one with the arrays, you were getting an array of arrays. You don’t want that. You want it to flatten, and that’s what flatMap
gives you.
Until now there is nothing special about flatMap
or monads. It’s just a way to message the data so the types look okay, and you have the right data structure.
With Result
, these functions take a simple value and return you the monad or functor:
func userForName(name: String) -> Result<User, NSError>
func avatarForUser(user: User) -> Result<UIImage, NSError>
let myName = "Raheel"
userForName(myName).flatMap(avatarForUser)
These functions themselves are not transformers. These are actually doing work. In the case of an optional, it was also clear that they are doing something.
Why is it an optional? Because something is happening. It’s not a simple transformation; there is probably a database lookup or something is happening. Now with the result, it’s clearer. There is an error. Why did the error happen? Some work failed or something.
I want to point out that these are not transforming functions anymore that you’re passing to flatMap
. Same thing as the optional, you have a name, and you want to eventually get an image, and you use flatMap
.
Signal (27:57)
Then we come to signals. Signal is probably the best place to study why we have a monad. Signals are not containers like the other ones were: optional, arrays, and result. They are operational or computational contexts. They do things.
enum Event<Value, Error> {
case Next(Value)
case Failed(Error)
case Completed
case Interrupted
}
First of all, an Event
. An Event
is the result type, but it has a few more cases. An Event
represents something that a signal emits. If you subscribe to a signal, you’ll get one of these four things: you’ll get a Value
if there was a value, or an Error
, or we’re done with the signal, or we have failed or something. This is how you subscribe on a Signal
:
class Signal<Value, Error> {
…
func on(event: Event -> (),
next: Value -> (),
failed: Error -> (),
completed: () -> (),
interrupted: () -> ())
}
You say on
and provide a closure for each one of the things that you’re interested in. This is an example:
let numbersSignal: Signal<Int, SomeError>
numbersSignal.on(
next: {
print("Received \($0)")
},
failed: {
print("Error \($0)")
}
)
You have a numbersSignal
. It’s going to emit events of the type Int
. You can observe it using this same function on
and then for next
you want to do something. When it fails, you want to print the error. If you’ve done any Reactive programming or features based programming, you’ll be familiar with this. Now flatMap
over Signal
:
func userForPredicate(predicate: Predicate) -> Signal<User, NSError>
func avatarForUser(user: User) -> Signal<UIImage, NSError>
let predicate: Predicate
You have two functions that the signal represents some work. One is, you have a predicate and based on that predicate you’re asking for a Signal. Maybe you’re observing the database, and whenever a user with that predicate match gets inserted or updated, you want a signal for that user.
That, in Reactive Cocoa, is more like a signal producer, but in any case, for a user you want the UIImage, you want a signal of UIImage’s. Again, the same semantics and pattern follow, for optional and arrays. You’ve got a predicate, and what you want is the avatars for all users that match that predicate. You want to compose those two functions or two signals.
extension Signal {
func flatMap<U>(transform: Value -> Signal<U, Error>)
-> Signal<U, Error>
}
I’m just repeating the flatMap
signature for a signal that we saw earlier. These functions, the signature, they take a simple value and return you a signal. We can use flatMap, and this is how to compose it:
func userForPredicate(predicate: Predicate) -> Signal<User, NSError>
func avatarForUser(user: User) -> Signal<UIImage, NSError>
let predicate: Predicate
userForPredicate(predicate) // Signal<User, NSError>
.flatMap(avatarForUser) // Signal<UIImage, NSError>
You can read Reactive Cocoa or some other libraries in terms of how flatMap is implemented for Signals or Promises. Also, they have documentation about flat mapping strategies.
Here you’re saying whenever you get a user, give me an avatar for it. It’s very declarative. It also manages failures internally. As in, if a failure happens in the first signal, it will bubble up in the composition.
It’s very difficult if you use anything that doesn’t support flatMap, to chain operations like this. Try chaining NSOperations. It’s really painful. How do you pass data from, or failure even, from one NSOperation to the next? You can set up dependencies, but how do you actually pass the data and how do you maintain failures? You have to do a lot of subclassing and all of that machinery.
Conclusion (32:33)
Monads provide you a common pattern. It’s very useful to recognize the monadic pattern, and sometimes it may not be called flatMap
, or bind
, as in some languages. For example, if you have done any jQuery programming, the $
is monadic. If you have done any D3, the JavaScript library, the select
in that is probably monadic. There are a bunch of these things that you can start recognizing across contexts, problem, discipline. I read papers on people recognizing monads in cooking or architecture.
Just to recap, functors chain transformations of contained value and monads chain operations on contained value. The composition is the key word. In Swift you can’t represent a monad as a monad, you can’t have a monad protocol. You need two more things to theoretically, at least from the Haskell standpoint, call something like an array a monad. One is a pure function, which is very simple, as in it takes a value and it returns something.
func pure<T>(m: T) -> Optional<T> {
return Optional<T>(m)
}
// let m: Int? = 23
It returns the monad with the value put on it. You’re assigning it, the assignment itself makes a monad out of that 23
. You get an optional. There are some basic, very simple monadic laws around associativity and all that, that are very simple to prove that this container actually does satisfy the monadic law.
If you read about anything that has a flatMap is a monad that may not be true, because a set is not a monad. That’s because of the laws. In particular, the set doesn’t work for all value types, it only would work for ordered types, at least in Haskell, something that can be ordered. Again, that’s not necessarily for Swift or other languages; this is just more Haskell terminology.
There is no real monad thing. You can’t say any array conforms to this monad protocol, and so on. You can’t write a function that you can say it takes a monad because it has a flatMap function. That has to do with needing something like this, higher kinded types. It has to do with the value that gets returned from the transform function.
In any case, if you want to see where we could possibly end up, look at something like Scala which does have higher kinded types. Scala-Z is a library that builds on top of that and then provides functors and monad types. Also, I would say that doing all of this work is in a tension with methods and protocol-oriented programming. A lot of these functions that we have seen are standalone free functions. You would most of all implement them as methods on those types. Then the composition starts to look a little wonky, but you can declare your custom operators and cure that problem.
Does it matter? Maybe not. If you’re an app developer, you can just keep using the libraries like always, and you’re using a monad, and you don’t think about it. That’s totally fine. But if you’re writing libraries, you’re writing your custom data search, and you’re making it available to other users, write a flatMap. Recognize that’s there.
Libraries in C# like Link, that evolved out of the idea of monads first, because they recognized these patterns and the idea of composition. Pattern recognition matters. Being able to carry that context and that shared vocabulary matters a lot across languages and across libraries and so on. It’s also a really cool gateway to functional programming and category theory.
Receive news and updates from Realm straight to your inbox