Monads explained to my team — Part 2: Endofunctors
Lomig
Posted on July 7, 2022
After talking about what monoids are in Part 1, and discovering that it was just a smart word for very simple things, let's discover the marvellous world of functors in this second part!
Fancy words for simple concepts
Developers uses functors everyday unknowingly, so let's figure out what this is all about.
#map
We all know #map
, a method that we use and love as Rubyists.
It allows us to take a structure of any kind (in terms of Ruby, an Enumerable
), and from within this structure, apply the same function to each of its elements.
savings_in_€ = [24.30, 350.90, -105.00]
#Brexit!
savings_in_£ = savings_in_€.map { |amount| (amount * 0.85).round(2) }
#=> [20.66, 298.27, -89.25]
savings_in_£.map { |amount| "£#{amount}" }
#=> ["£20.66", "£298.27", "£-89.25"]
The rules of #map
Of course, #map
could be called anything else — the important part of this method is not its name, but the fact that it must follow some rules to be considered a real map
!
Those are the two rules:
- Rule of identity
any_array.map { |x| x } == any_array
- Rule of composition
def multiply_by_two(x) = 2 * x
def add_three(x) = x + 3
a = [2, 5, 7].map { |x| multiply_by_two(add_three(x)) }
b = [2, 5, 7].map { |x| add_three(x) }
.map { |x| multiply_by_two(x) }
a == b # [10, 16, 20]
So, what about Functors?
A functor is:
- a structure that is
mapable
- that is polymorphic (ie that can contain any type)
In Ruby, it means that:
- Arrays are functors
- ✅ can map internal elements
- ✅ respect map rules from above
- Hashes are not functors
- ✅ can map internal elements
- ❌ does not respect map rules
And Endofunctors, then?
An endofunctor is a functor whose #map
function will return a structure with the same type.
In Ruby, it means that Arrays are endofunctors
* ✅ takes an Array
, and returns an Array
As a Rubyist, why do I care?
Functors, and more particularly endofunctors, allow for function composition and make it safe to chain any number of mapping:
Array.map { |x| x * 17 }
.map { |x| x / 3 }
.map(&:round)
.map(&:to_s)
Ok, but for God's sake, what's the relevance with Monads?
Well, obviously, it's the definition of a monad!
But that's for the next article!
Posted on July 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.