Every developer which starts learning functional reactive programming eventually faces such concepts, like functor, applicative and monad. For the first time, they sound really scary, but there is nothing complex behind these words. In today’s lesson, I will try to explain to you these simple, but not very familiar concepts for imperative programming.
To make this article more understandable we will write together some code. Because from my perspective, programmers perceive and assimilate information better, when they can try it in their favorite IDE. So, my examples will be very simple, but it will be enough to understand the basics of further learning.
Values in context
If you are an imperative programmer, you are basically familiar with properties. Each property has a type and it’s own value. In the functional world, this concept is a little bit different. So, first of all, we need to understand what does it mean value in a context. For simplicity, imagine a variable, for example, var number = 7. And then, imagine a box, where you can put your value.
This box is a simple concept of our context. The next thing, you should accept, is that our box can have a value, or can be empty (like an array or real box from the physical world). The value wrapped in a context means that we can’t reach this value directly, but we can try to extract it somehow, in case our container is not empty. Nothing complicated here — everything is pretty simple and understandable.
I hope, that at this moment everything is clear for you. An attentive reader can notice, that our box concept resembles Optional type in Swift. And he will be absolutely right! Like a box, Optional can have a value or can be nil, if a value is absent.
A little spoiler: Optional is a functor and even a monad.
But first things first. Let’s create our own optional-like type, and on its example learn how to deal with functors, monads, and applicatives. For further experiments, we define generic enum of type Box<T> with two cases: some — when we have a value, and empty — when our box is empty.
If everything is clear for you, it’s time to introduce a functor.
In a nutshell: functor is a type, that implements map function. Like Optional, Collection and Result types in Swift.
Okay, now imagine that you have a variable of type Int that is equal to 7. And then you want to increase it by 3, to get 10. What do you do in the imperative paradigm? Right, you just write 7 + 3 and you get your 10. But how can we add 3 to our value 7, which is wrapped in a context?
Box(7) + 3 -> What? Type Error.
To do such kind of computation, we need some function, that will execute computation in a case when the box is not empty, or fail/return “empty box” if the box has no value inside.
What we’ve done here: we define add a function that accepts Int value as a parameter, perform some computation and returns Box of the same type Int. Pretty straight-forward. Now, we can rewrite this expression to make it more generic. Here, where map function comes in. What we want to achieve: is to pass any function with the signature (T) -> U as a parameter to map and to become independent of a specific type. That’s how we do it:
What we have here:
- generic map function that accepts function of type (T) -> U as its single argument and has a return type Box<U>
- in the function body, we do simple switch case
- if there is a value, we call f-function on it, get a result of type U, wrap it back to Box<U> type and simply return it
- if there is no value, we just return an empty case of new type U: Box<U>
Now using map-function, we can apply any function to our “box” that accepts T as a parameter and returns a new U type. Thereby we get an ability to deal with value wrapped in a context. And now we are ready to define a functor.
A functor applies a function to a value wrapped in a context.
In other words: functor is a type, that implements map function which knows and describes how to apply a function to a wrapped value.
If at this point everything is clear for you, then you are ready to learn what is applicative. If not, feel free to read about functor again, make your favorite coffee and come back to this part of the article later.
In the previous chapter we’ve learned about values in context and how to apply functions to them using functors. But what should we do if we want to apply a wrapped function to a wrapped value? Now we need something more powerful than the map. Let’s extend our Box type with apply function, which will simply switch on function in context and on a value in context.
Now pay attention to the first some case. Does nested switch statement remind you of anything? This code does pretty the same our map function does. So let’s refactor it a little bit and replace the nested switch with the map function. Excellent!
So our computation will return some value wrapped in a context only in case if there is some function in a parameter box and some value inside our box. In other cases apply will return empty box. Sounds good? Let’s define what applicative is. I hope at this point you can make a definition of this concept by yourself.
Applicative applies a wrapped function to a wrapped value.
Again, if everything is clear for you, we can move forward.
Very good. Now we have the last and the most hard-sounding concept — monad. Just read its definition and try not to get mad:
A monad applies wrapped function that returns wrapped value to the wrapped value.
But don’t be afraid, you’ve already successfully learned about functors and applicatives. I promise the monad will be a piece of cake for you. Let’s define flatMap function for Box type and see, what exactly monad is.
The argument of flatMap function by itself returns Box<U>, not U-type as functor’s or applicative’s parameter function does. So the monad does exactly what it should do: it takes a function that returns wrapped value and applies this function to the wrapped value.
After learning these concepts, it should be clear for you, that optional, array even Result types in Swift all are functors and monads. Why? Simply because they implement map and flatMap functions, which in turn know how to perform computations when there is a value inside or there is an absence of value.
Let’s see, how it can be useful in your daily work as an iOS Developer. For example optional. Imagine some function that requires integer as a parameter and do some stuff with it. But instead of an integer value, we have our Int wrapped in optional type. What do you usually do, is something like this?
But how can we refactor this code? Let’s simply use our new knowledge and try map function, which applies a function to a wrapped (optional in our case) value.
Now our code looks better, shorter and more descriptive. Good, next we will try another example.
Check out the type of someModel property. String?? Double optional. Wait, what? Our optional try? The function returns an optional String, and the map function wraps it also into nested optional, which has no sense. So how we can refactor it and flatten nested optional? We should use something, that applies a function to the wrapped value, that by itself returns wrapped value. Doesn’t resemble anything? Exactly, it’s the monad, which provides us with flatMap function:
Or even more complex nested optionals. Here the result type is Optional<Optional<Int>>??
But, using flatMap instead of each map function, we can flatten and reduce our nested optional to the single optional resulting value. The same thing is with the Result type in Swift. It is also a functor and a monad. Just look at this code:
I hope this article was interesting and helpful for you. If you received any new knowledge or you still have some questions, write about it in the comments.