<!-- theme: uncover -->
<!-- class: invert -->
# Monads: <br/>What? Why? Where? When?
---
## Who's under the beard?
---
<img src="https://hackmd.io/_uploads/rJhpp_Jnh.jpg" alt="Denis Dubo Chevalier" width="200" style="text-align: center; margin: 0 auto" />
Denis Dubo Chevalier
- Staff software engineer :construction_worker:
- :heart: Vim
- :heart: Simple processes :train:
---
## Agenda
---
- Monads: What are they?
- Why do they matter?
- Where should we use them?
- When shall we use them?
<small>Credit is due: This talk is heavily inspired by [John Gallager's talk on the same topic at RubyConf 2021 Denver](https://www.youtube.com/watch?v=bj7yH0KhEww)</small>
---
:warning:
I will be oversimplifying some of the underlying concepts.
---
## Monads: What are they?
---
### Definition
---
> The monad \[...\] is nothing but a simple substance that enters into composites; simple, that is, without parts. And there must be simple substances, since there are composites \[...\] These monads are the true atoms of nature and, in a word, the elements of things.
<small><a href="http://philosophyfaculty.ucsd.edu/faculty/rutherford/Leibniz/translations/Monadology.pdf">Leibniz, Monadology</a></small>
---
Oops :open_mouth:
---
Wrong monads :fire:
---
> A monad is a monoid in the category of endofunctors.
---
There, that's the one :smiley:
---
But it's over simplified :confused:
---
> All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.
<small><a href="https://books.google.fr/books?id=MXboNPdTv7QC&pg=PA138&lpg=PA138&dq=%22monoid+in+the+category+of+endofunctors%22+mac+lane&source=bl&ots=feQWTkH2Uw&sig=tv-1JwaMOygKGmFE2vM2FhJVS9o&hl=en&ei=5iWsTJCkBIPSsAPQwJ36Aw&sa=X&oi=book_result&ct=result&redir_esc=y#v=onepage&q&f=false">Saunders, Categories for the Working Mathematician</a></small>
---
Let's focus on the simplified definition for now :smile_cat:
---
A monad is a what in the what of what :question:
---
Don't worry, it's actually really simple!
---
> A monad is a monoid in the category of endofunctors.
---
### What is a category?
---
Let's do some maths!
---
A **category** $\mathcal{C}$ consists of:
<div style="font-size: .75em">
1. A collection $obj(\mathcal{C})$ of objects;
2. For each pair $A,B \in obj(\mathcal{C})$, a collection $\mathcal{C}(A,B)$ of **morphisms** from $A$ to $B$, denoted $hom(\mathcal{C})$;
3. For every $A,B,C \in obj(\mathcal{C})$, a binary operation $\mathcal{C}(A,B) \times \mathcal{C}(B,C) \to \mathcal{C}(A,C)$ called **composition of morphisms**, denoted (for two morphisms $f:A \to B$ and $g: B \to C$) $g \circ f: A \to C$.
<p style="text-align: left;margin-top:1ex">Satisfying <b>associativity</b> of morphism composition and <b>identity</b> morphisms for each morphism/object pair.</p>
💡 A morphism is often a function.
</div>
---
In plain english, it's a set of objects and operations on those objects that can move from one to another and support composition.
---
For instance, the set of all integers and the arithmetic operators. Or a set of destinations, and the movement from one to another, etc.
---
### What is a monoid?
---
First of all, a monoid is a category, but with some more constraints.
---
Let's do some maths (again 😓).
---
$$1 + 2 = 3$$
---
So we have two things of one kind, and when we apply some operation (a way of combining them), we get another thing of the same kind.
---
Too simple?
---
$$1 + 2 + 3 = 6$$
---
As we have the same kind in input and output, we can do it for more than two things.
<small>So we can apply it to lists, it is **reducible** 🤓</small>
---
Still too simple?
---
$$(1 + 2) + 3 = 1 + (2 + 3)$$
---
We can put parenthesis however we want, it doesn't change the result.
<small>So we can parallelize the computation, it is **parallelizable**
🤓</small>
---
:warning:
$$(1 - 2) - 3 \neq 1 - (2 - 3)$$
---
One last step!
---
$$1 + 0 = 1$$
$$0 + 1 = 1$$
---
There is some value that make it return itself.
<small>There is an **identity** value 🤓</small>
---
That's it, here is a monoid:
<small><ul>
<li> A <b>category</b> of things that can be combined via an operation that gives another thing of the same type</li>
<li> Where the combination is <b>reducible</b>.</li>
<li> And where the combination is <b>parallelizable</b>.</li>
<li> And where exists an <b>identity</b> value.</li>
</ul></small>
---
Like the set of all integers and the addition operation.
<small>But not the substraction operation.</small>
---
Simple!
---
### And what is an endofunctor?
---
#### What is a functor?
---
For all intents and purposes, it's a function object that takes an input and returns an output.
<small>For the purists, there is more to it, a Functor is a **mapable** function object 🤓</small>
---
#### What is an endofunctor?
---
It's a functor that takes an input of a type and returns an output of the same type.
---
```ruby!
square = ->(x) { x * x }
```
---
### So, what's a Monad?
---
> A Monad is just a monoid in the category of endofunctors
---
To put it simply, a Monad is a **Chainable** construct. We use it for **composition**.
---
And we should always chose composition over inheritance :wink:
---
Let's see it in practice!
---
## Why do Monads matter?
---
### Let's see an example
---
```ruby!
class FindContact
def call(id)
response = Rails.configuration.api
.get("/contact/#{id}")
JSON.parse(response.body).fetch('contact')
rescue Faraday::Error, JSON::ParserError
nil
end
end
```
---
Yeah! We handled all errors!
---
But is our error handling any good?
---
Let's see another example.
---
```ruby!
class LookupPostdode
def call(postcode)
response = Rails.configuration
.get("/addresses/#{postcode}")
JSON.parse(response.body).fetch('addr')
rescue Faraday::Error, JSON::ParserError
[]
end
end
```
---
Now we return an empty list on error !?
---
Let's see a controller for those two classes.
---
```ruby!
class AddressesControler < ApplicationController
def edit_addr
contact = FindContact.new.call(params[:contact_id])
render :edit_with_error and return if contact.nil?
addr = LookupPostcode.new
.call(contact.fetch('postcode'))
if addr.any?
render :edit_with_address_select,
locals: { addr: }
else
render :edit_with_manual_address_entry
end
end
end
```
---
There are a few problems.
---
We need to be aware of `nil`.
---
> I call it my billion-dollar mistake. It was the invention of the null
> reference in 1965.
<small><a href="http://qconlondon.com/london-2009/speaker/Tony+Hoare">Tony Hoare, 2009</a></small>
---
Have you tried to call a method on nil? It will blow up.
<small> _With the exception of `.nil?`_</small>
---
Also, we have a strong coupling between the controller and the service classes.
---
```ruby!
class FindContact
def call(id)
response = Rails.configuration.api
.get("/contact/#{id}")
JSON.parse(response.body).fetch('contact')
rescue Faraday::Error, JSON::ParserError
nil
end
end
```
What is the difference between an inexisting contact and a timeout trying to get one?
---
**We have no convention as to what the return value should be.**
---
So we ship broken software :cry:
---
### Let's fix this code
---
## Where should I apply Monads?
---
We'll do it in the same order:
---
```ruby!
class FindContact
include Dry::Monads[:result]
def call(id)
response = Rails.configuration.api
.get("/contact/#{id}")
Success(JSON.parse(response.body).fetch('contact'))
rescue Faraday::Error, JSON::ParserError => e
Failure(e)
end
end
```
---
Not much change, right?
---
Let's do the same thing for `LookupPostcode` class.
---
```ruby!
class LookupPostcode
include Dry::Monads[:result]
def call(postcode)
response = Rails.configuration
.get("/addresses/#{postcode}")
Success(JSON.parse(response.body).fetch('addr'))
rescue Faraday::Error, JSON::ParserError => e
Failure(e)
end
end
```
---
Now let's see the power of those `Failure` and `Success` thingies.
---
```ruby!
class AddressesControler < ApplicationController
def edit_addr
FindContact.new.call(params[:contact_id])
.or { |e| render :edit_with_error and return }
.fmap do |result|
LookupPostcode.new.call(result.fetch('postcode'))
.or { |e| render :edit_with_manual_addr_entry }
.fmap do |addr|
render :edit_with_addr_select,
locals: { addr: }
end
end
end
end
```
---
Nani? :confused:
---
`Success` and `Failure` are two types that follow the same interface, `Result`.
The `Result` interface is a **Monad**. It allows us to convert our program flow to a pipelines flow.
<small>It's a combinator that allows chaining of operations.</small>
---
Result defines a few methods:
<small><ul>
<li><code>Result.success?</code> Returns true if the value is a Success.</li>
<li><code>Result.failure?</code> Returns true if the value is a Failure.</li>
<li><code>Result.value!</code> Gives the underlying value if the value is a Success, throws an exception in case of failure.</li>
</ul></small>
---
We handle Success with `.fmap()`:
<small> `fmap` stands for flat map, it is also synonymous to `bind`.</small>
---
`fmap` takes a method of the underlying type and returns an object of the same type, wrapped into the result, if the result is a `Success`. It returns the original object otherwise.
```ruby!
Success(42).fmap { |x| x * 2 }
# => Success(84)
Failure(:divide_by_zero).fmap { |x| x * 2 }
# => Failure(:divide_by_zero)
```
---
We handle Failure with `.or()`:
---
`or` takes a method on an error and applies it to the failure. It then returns the original object. It only returns the original object on success.
```ruby!
Success(42).or{ |e| Airbrake.notify(e) }
# => Success(42)
# side effect: none
Failure(:divide_by_zero).or{ |e| Airbrake.notify(e) }
# => Failure(:divide_by_zero)
# side effect: Airbrake.notify(:divide_by_zero)
```
---
And we can chain them!
---
### To recap
---
We went from this:
```ruby!
class AddressesControler < ApplicationController
def edit_addr
contact = FindContact.new.call(params[:contact_id])
render :edit_with_error and return if contact.nil?
addr = LookupPostcode.new
.call(contact.fetch('postcode'))
if addr.any?
render :edit_with_address_select,
locals: { addr: }
else
render :edit_with_manual_address_entry
end
end
end
```
---
To this:
```ruby!
class AddressesControler < ApplicationController
def edit_addr
FindContact.new.call(params[:contact_id])
.or { |e| render :edit_with_error and return }
.fmap do |result|
LookupPostcode.new.call(result.fetch('postcode'))
.or { |e| render :edit_with_manual_addr_entry }
.fmap do |addr|
render :edit_with_addr_select,
locals: { addr: }
end
end
end
end
```
---
We have gained:
- Cleaner code
- Better error and exception handling
- Better flow
---
And I only showed you one application of the monads!
---
### So to answer the question, where should I use Monads?
---
Whenever I have an object that could have two or more kinds of status:
- A nilable,
- A success/failure,
- An asynchronous task,
- Etc.
---
Whenever I want to encapsulate side effects.
---
Whenever I want composition over inheritance: Monads work by composition!
---
Whenever I want to show a clear flow by chaining operations.
---
Finally, when I want to be certain that I handle all possible failures.
---
## Ok, this is great, but I don't have time to do this right now
---
That may be true.
---
We won't change our code base in a day.
---
But we can still learn from this pattern!
---
And maybe apply it to some new code.
---
## :100: :muscle: :tada:
---
### Wrap up
- Monads are simple :tada:
- As a developer we should be wary of the flow of our programs;
- And _KISS_.
---
## Thank you! :sheep:
---
Going Further:
<small><ul>
<li><a href="https://dry-rb.org/gems/dry-monads/1.6/">The Dry::Monads documentation</a></li>
<li><a href="https://github.com/denisdubochevalier/monad/">My own monad go package toy project</a></li>
<li><a href="https://www.youtube.com/watch?v=Nrp_LZ-XGsY"> Scott Wlaschin's The Functional Programmer Toolkit talk</a></li>
<li><a href="https://www.youtube.com/watch?v=3VQ382QG-y4">Gabriel Lebec's Fundamentals of Lambda Calculs talk</a></li>
<li><a href="https://www.youtube.com/watch?v=pAnLQ9jwN-E">Gabriel Lebec's A Flock of Functions talk</a></li>
</ul></small>
---
$$Y=\lambda{f}.(\lambda{x}.f(x\ x))(\lambda{x}.f(x\ x))$$
{"breaks":true,"description":"Tech sharing session","title":"Monads: What? Why? Where? When?","contributors":"[{\"id\":\"4ac2068d-3dc3-414e-8fde-789582d34f1e\",\"add\":32572,\"del\":34134}]"}