Monads In Ruby, Part 2: Identity, the Littlest Monad
Illustrated with excerpts from the unpublished novel The Oilcloth Merchant.
What Do We Know?
From The Oilcloth Merchant, Chapter 4
Peter stood on the bank by the river and surveyed the greying sky. For five generations his family had owned the land he stood on. Now he stood to lose it all. How could he tell Bernice? What about the orphans?
He sat down on the worn wall that ran the length of the footpath on the side of the river. A herd of mackerel clumsily scuttled down the embankment, toward the water’s edge. He smiled as he remembered chasing them across the lawn with Cyril, when they were still boys. The herd divided before them with squwaks and awkward flutterings. Laughter. They ran in wide circles, the boys tiring long before the canny fish.
But they’d grown up now, and Cyril had changed. Maybe they’d both changed. And now he had to formulate a plan. Do something, and the first step of this was to fix in his mind what he already knew.
Okay, so. What do we know about monads?
- They can be made from pretty much any sort of generic container.
- You can wrap a single value in a monad (
wrap) - You can extract values from a monad and give them individually to a function to process and repackage (
pass)
Let’s build a monad. Easy on the eyes. We’ll do this one piece at a time.
First, let’s make the simplest container we can. For once, we’ll call our monad the same thing as its Haskell equivalent. Which means Identity.
Now, this monad is so simple that it isn’t really useful, but it’s here to show us where all the pieces fit. We’ll get to more interesting monads soon enough.
class Identity
def initialize( value )
@value = value
end
end
Can we make a simpler container? Probably not. We haven’t even provided a way to get a value back out yet—we’ll get to that in due time.
We’ve got our container. Let’s set about the task of turning it into a proper monad.
wrap
Yeah. First thing, make a way to wrap a value in our monad. Identity::new puts a value in an instance of Identity, right?
We’ll use that to make Identity::wrap.
def Identity::wrap( value ) ; new( value ) ; end
Yes, that easy.
pass
Second thing, we’ve got to make a method that extracts the contents and gives them to a function. This being Ruby, that may as well mean a block.
class Identity
def pass
yield @value
end
end
Total simplicity… but it can’t be that easy?!
Yes Virginia, it can be. Well, almost. There are a few rules we need to follow for everything to work out. But let’s take a breather first. You done good. Get up, stretch, get something to drink. Then come back and skim the above again so it’s fresh in your mind.
...
Back? Okay. Here are the rules that make monads work.
The Monad Laws
First rule of Monad Club: don’t talk about Monad Club.
This is a rule for the blocks that we give to pass. They’re in a privileged position. They get to see what’s inside the monad, but they’re not allowed to leak information to the outside world. At least not in their result value.
From The Oilcloth Merchant, Chapter 28
Bernice sat quietly, fingering the parchment her visitor had delivered, secure from prying eyes, in its little velvet box. Another such box lay beside her on the floor, open and waiting. Her reply, the instructions said, must be similarly conveyed.
Rising slowly, she made her way to the dresser. From the top drawer she removed a large brick (her very best, she noted with some sadness) and fit it carefully into the box. On went the lid. She tied the ribbon quickly; there were so few hours left. Oh, Peter… will you be able to save the orphanage in time?
Composing herself, she descended the stairs to find her masked guest standing silently where she had left him. He took the box without remark.
“Sir, please deliver this quickly to the Count. He has my reply.”
“Madam.” With that, her visitor was gone.
So, our privileged little block has to produce a result, and that result has to be wrapped back up in the monad, like so:
someidentity.pass do |value|
Identity::wrap( value + 1 )
end
[ If you don’t have a result worth mentioning, you can always just wrap nil. ]
There are also three laws that wrap and pass have to follow to make Identity a real monad.
- Calling
passon a newly-wrapped value should have the same effect as giving that value directly to the block. passwith a block that simply callswrapon its value should produce the exact same values, wrapped up again.- nesting
passblocks should be equivalent to calling them sequentially
Let’s look at each of these in detail. For our purposes, we’ll assume that f and g are two functions which take a value, do something with it, and return an already-wrapped result.
The First Law: wrap as a left-identity for pass
Jargon, yum. This just means that:
Identity::wrap( blah ).pass do |value|
f( value )
end
Should have exactly the same effect as:
f( blah )
The Second Law: wrap as a right-identity for pass
This just means that:
someidentity.pass do |value|
Identity::wrap( value )
end
Should produce an instance of Identity with the same contents as the original (someidentity). In other words: can we trust pass and wrap not to change the values on their own?
The Third Law
The third law just means that chaining pass blocks should produce the same effect as nesting them. To wit:
someidentity.pass do |value_a|
f( value_a )
end.pass |value_b|
g( value_b )
end
and
someidentity.pass do |value_a|
f( value_a ).pass do |value_b|
g( value_b )
end
end
Does Our Monad Measure Up?
From The Oilcloth Merchant, Chapter 33
Peter weighed the sword in his hand. A slight turn of the wrist, and its point traced a small, neat circle in the air. This one would do.
Now the trial.
I think our little monad will pass. Do you? Do you believe?
Law One
Identity::wrap(blah).pass do |value|
f( value )
end
Same as f(blah) or not?
Well, wrap just stores the value of blah in an Identity’s @contents, then pass takes it right back out and gives it to the block. The block gives it directly to f.
That part works out.
Now for the home stretch. Does the result get out as it should?
The block returns the result of f, and pass (for Identity, at least) doesn’t need to do anything further with it, so it returns it directly.
Feel! Our little monad is making it!
Law Two
someidentity.pass do |value|
Identity::wrap( value )
end
Same as someidentity or not?
Sure is. We take @value out of someidentity and just wrap it right back up again.
This is easy! But…
Law Three
someidentity.pass do |value_a|
f( value_a )
end.pass do |value_b|
g( value_b )
end
someidentity.pass do |value_a|
f( value_a ).pass do |value_b|
g( value_b )
end
end
Same or not!?
Do you believe!?
Well, we know in the first one, that first pass will simply return f(value_a), and we call pass on that with that other block calling g(value_b). Which… is… exactly… what the second one does!
Believe!
Look at our little Identity! It’s struggling so hard!
BELIEVE!
Its wings have sprouted! It takes to the air! It is a monad!
From The Oilcloth Merchant, Chapter 40
The flames rose higher against the sky, tinting the low-hanging clouds a dull orange. Bernice ran faster. No, No, NO, NO! She crested the hill and stopped, the pit of her stomach tightening. The orphanage was a mass of roaring flame.
“The children are safe,” said a figure silhouetted against the flames. After a moment, she recognized the outline of the masked footman’s overcoat. “YOU!!!” she spat.
He stepped closer and in the flickering light, for the first time, she saw his face. “The children are safe,” he repeated. “I was able to get them out in time.” Behind him stretched a line of sleepy children, huddled on the hillside.
Her mouth sagged open. “Peter!”
They embraced for the longest time before another thought occured to her. “What will we do for them now?”
“We will have the manor,” he said matter-of-factly. “I’ve got the authentic will.” Reaching into his coat, he produced a sooty tin of marmelade. In the oblique firelight the words engraved on its lid stood out, scribed in the most careful legal hand, doubly witnessed.
“What about the Count?”
“Cyril?” Peter looked back at the burning orphanage sadly. “The victim of his own scheme. I would have spared him, Bernice. I would.”
One Last Thing
Now that Identity is a really, truly monad, let’s grant it one last thing before it flutters away. Most monads do give you some way to get a value out directly, the details of which vary from monad to monad since they all work differently.
For Identity, though, it can be a simple unwrapping.
class Identity
attr_reader :value
end
Fly free, little monad.