Monads explained to my team — Part 3: Creating a monad
Tags: ruby, functional
Published: 2022-07-07 20:52:35

Monoid, Endofunctors… Lots of theoretical pieces have been exposed in the 2 previous parts, but it's time to try and practice with Monads.

  
## Once upon a time, there were boxes

Let's create a box, to store things like numbers.
* The initializer puts the value inside the box
* `Box#value!` unbox the value and gives it back.

```ruby
class Box
  private attr_reader :value

  def initialize(value)
    @value = value
  end

  def value! = value
end
```

  
## This box does not belong to Schrödinger

That looks utterly useless! So let's add a concept: these boxes can be full (with a value), or empty (value being `nil`).

So, what if we want to add 2 to such a boxed number?
* The box is a container
* We have to take its elements (even though there is only one here) and apply our function to add 2
* We put back the element in the box, and then we return it

… and what if the box is empty? Well, we return an empty Box, then!

```ruby
class Box
  private attr_reader :value

  def initialize(value)
    @value = value
  end

  def value! = value

  def add(x)
    return self unless value

    Box.new(value + x)
  end
end

Box.new(nil).add(4) #=> #<Box:0x0000000 @value=nil>
Box.new(130).add(4) #=> #<Box:0x0000000 @value=134>
```

  
## Making the box content operation-agnostic

The code above is fine, but if we want to do something else than adding numbers, we would have to add methods all over for each operation. We can do better.

Is there a way to extract something from a container, apply a function to it, and put it back? It was the subject of the previous article: let's make it a functor!

```ruby
class Box
  private attr_reader :value

  def initialize(value)
    @value = value
  end

  def value! = value

  def map
    return self unless value

    Box.new(yield(value))
  end
end

Box.new(nil).map { |x| x + 4 } #=> #<Box:0x0000000 @value=nil>
Box.new(130).map { |x| x + 4 } #=> #<Box:0x0000000 @value=134>
```

  
## Nested boxes are the worst…

So, with a Box, we can perform actions on the content without any need to trouble ourselves checking for `nil` values where we are not in control!

```ruby
def double(x) = 2 * x

user_count = Box.new(UserCountAPI.call)
#=> #<Box:0x0000000 @value=25>

shoes_count = user_count.map { |x| double(x) }
#=> #<Box:0x0000000 @value=50>
```

It's cool enough for us to use the `Box` at some different places in the code. But sometimes, it could create unforeseen problems; for an example, when we are unaware that a method already uses a `Box`:

```ruby
def double(x) = Box.new(2 * x)

user_count = Box.new(UserCountAPI.call)
#=> #<Box:0x0000000 @value=25>

shoes_count = user_count.map { |x| double(x) }
#=> #<Box:0x0000000 @value=#<Box:0x0000000 @value=50>>
```

… a box inside a box? That's a problem!

To avoid such an issue, we would needs to implement a different `#map` method that would flatten the result: `#flat_map`

```ruby
class Box
  private attr_reader :value

  def initialize(value)
    @value = value
  end

  def value! = value

  def map
    return self unless value

    Box.new(yield(value))
  end

  def flat_map(&block)
    map(&block).flatten
  end

  def flatten
    return self unless value

    value
  end
end

Box.new(nil).map { |x| x + 4 } #=> #<Box:0x0000000 @value=nil>
Box.new(130).map { |x| x + 4 } #=> #<Box:0x0000000 @value=134>
Box.new(130).flat_map { |x| Box.new(x + 4) } #=> #<Box:0x0000000 @value=134>
```

And there we have it! **A monad is just that**:
* a `mappable` `Box`,
* a method (`Box.new`) to put something in the `Box`,
* a `flat_map` method as an extension of the `map` method.

That's a lot of fuss for something quite simple, right? 

  
#### Useless bit of information
Monads are indeed monoids!

* All `Box` instances (that are endofunctors) represent the monoid set
* `#flat_map` is a closure. As per the first article:
```ruby
Box.new(4).flat_map { |x| Box.new(2 * x) } #=> #<Box:0x0000000 @value=8>

# A_THING  COMBINED_WITH  ANOTHER_THING     => ANOTHER_THING
```
* `Box.new` is the identity element
```ruby
def double(x) = Box.new(2 * x)
content = 4

# == equality assuming we made Box be comparable
Box.new(content).flat_map { |x| double(x) } == double(content)

Box.new(content).flat_map { |x| Box.new(x) } == Box.new(content)
```
* `#flat_map` is associative
```ruby
def double(x) = Box.new(2 * x)
def triple(x) = Box.new(3 * x)
content = 4

a = Box.new(content).flat_map { |x| double(x) }
                    .flat_map { |x| triple(x) }

b = Box.new(content).flat_map do |x|
  double(x).flat_map { |y| triple(y) }
end

# == equality assuming we made Box be comparable
a == b 
```

  
## So what's next?

You can see that a monad is not that complicated a concept, even though its usefulness may not be clear right at once.

Don't worry, we'll develop real-ish example in the next article!