'M' is for Burrito. No, really.
(A little post about monads)
This one's easy.
A monad is a burrito: a nice, soft-shell wrapper hiding all that yummy, but complicated stuff inside (green peppers and onions? who was the first one to think of that?)
A monad is a burrito, wrapping all that complicated stuff like an integer:
Just 3
under its soft-shell wrapper:
[1,2,3]
I just showed you two monads: the first one is the 'Maybe' monad, representing semideterminism (we're certain, in this case, that the underlying unit is 'Just' 3, but we could, in a different situation, be equally certain that the underlying unit was 'Nothing' Those two cases (two = semi) give us our certainty (determinism) one way or the other), the second is the 'List' monad, representing nondeterminism (you could have the empty list [] (no answers, no = non), or, in the above case, a list with a set of different answers (one or several)).
So, monad = burrito. Simple, right?
Class dismissed.
"Excuse me, Professor geophf," the wormy student in the back of the class wearing b-c glasses and who always has to ask annoying questions in his whiny voice at the very end of class, making everybody late for their next class and me late for my latte, and you don't want to make me late for my latte! He continued, oblivious: "What about monads that don't contain anything at all? What is that? An empty burrito?"
He giggled at his own joke, thinking himself rather smart and drôle.
"What?" I roared, "An empty burrito? Such sacrilege cannot be! Besides, what's the point of an empty monad? That's just ridiculous!"
I was rather peeved that this young upstart would trespass on the beauty of my burrito-analogue. It was simple and easy to understand. Why mess with it?
"But," he continued in his whiny voice, "what about the empty list and 'Nothing' in the Maybe monad. They carry no value but are essential to each of those monads, how do you explain these monadic values as burritos? Are they churros?"
I sputtered in fury. He had me there.
So I did what every tyrant teacher does, I relied on my position of authority.
"No, monads are burritos! Get that through your head!" And then: "Class dismissed!"
Everybody fled. I do cut quite the imposing figure when I have that angry glint in my eye and my flinty face set in stone.
Okay, so monads aren't burritos. I admit it. (But not to Mr. Wormwood. Never, never!) There is nothing that says a monad has to carry a value, it's just a convenience to think of monads like that: a wrapper around an 'ordinary' object. But monads are objects, themselves, and, in fact, more 'ordinary,' that is: regular, than plain-old objects like integers.
The problem of plain-old objects, the problem that monads address, each in their own way (and there are many species of monads, not just maybes and lists, but eithers, and states and ... stuff! Lots of wild and weird different stuff!), is that plain-old objects admit the uninhabited instance, ⊥, in their type. So the type of natural numbers is 0, 1, 2, 3, ... but it's also ⊥. For programming languages that realize this (haskell, for example), they deal with this gracefully. For programming languages that realize this, but then do nothing about it, there is a systemic problem of the program going into an unstable state when encountering an uninhabited instance.
Something that causes Algol-child language programmers to shake in their booties: the 'null-pointer exception.'
Question: how can Java throw a 'NullPointerException,' when it doesn't have the pointer construct in its language, at all? Am I the first to call this one out?
So, the function in these languages that have ⊥ but do not handle it properly must always admit that you're not working with functions at all, for:
x + y
is x plus y, but if either in uninhabited, the answer is KABOOM!
A program crash. You just destroyed your entire system. And you didn't even do it: the language designers did.
Monads were an answer to this problem. A little dude with a long, bushy beard by the name of Kleisli developed the work around what came to be known as the Kleisli categories, that had monads as their objects and (what came to be known as) Kleisli arrows as morphism, or functions.
The guarantee of the Kleisli category was that, indeed, every object as a monad, so there's no uninhabited type, this means Kleisli functions always get you back into a Kleisli category (yes: once you enter the monadic domain, you never leave it. That's the guarantee of a Kleisli category, you can only be a monad (or a monadic function or monad category ...) to get in, and no monads exist outside the Kleisli categories. The monadic domain is the Hotel California of categories).
Okay, you're stuck. And the guarantee of the monad is that you don't care what's under the soft-shell, there's no 'getting a value out' of a monad, because, as my annoying student, Mr. Wormwood, pointed out, there are monads with no underlying values. So what do you do with them?
Monads have a special function associated with them, called the 'join' operator.
Monads, you see, are a special type in that a join of a monad of a monad is just a monad, so, for example,
join (Just (Just 3))
gives you
Just 3
What does that do for us?
Well, the structure of a monadic function is this:
Monad m ↠ f m :: a → b m
That is, the function takes an ordinary type a and 'lifts' it into the monadic category giving the (monadic) answer of type b (correctly m b, as m is the monadic type and b is the type carried by the monad).
Okay, but monadic functions can't dig out the underlying type in a monad of its category, so how do even the functions work at all?
Just as you can lift an unit type into a monad:
return 3 = Just 3
You can also lift an ordinary function into the monadic domain:
liftM succ = m Int → m Int (for some monadic type m)
Okay, so, but where does that even get us? We're still stuck, right?
Well, the thing is, you can even lift monadic functions into the monadic-monadic domain:
liftM f (of type Monad m ↠ a → m b) = f' :: Monad m ↠ m a → m (m b)
What does that get us?
Well, combining join with a lifted monadic function gets us a monad from monadic input:
join (liftM f (Just 3)) = some monadic answer dependent on the resultant type of f.
This whole lifting-into-the-monadic-domain-and-then-joining-the-result is so common when working in monad categories that a special operator was invented, à la Haskell, named '>>=' (pronounced: 'bind').
Now, with bind the true power of monads come out:
m >>= f >>= g >>= h >>= ... etc.
What you see here a monad being bound, in turn, to the monadic functions f, g, h, etc.
So, okay, you've got a short-hand for monadically binding functions together. Great. Good on you, and have a latte.
The thing here is this.
In 'plain-old' programming, you have this ever-present fear that a NullPointerException is going to ruin your life, so you can't write:
obj.getA().getB().getC().thenDoSomething()
getting an A from obj, then getting a B from that A, then getting a C from that B, then you do something with that C.
I mean, you can write that code, but if any of those objects are null: obj, A, B, C, your whole statement explodes?
No, worst than that: your entire system blows up, and you have to deal with irate customers wondering why they got a 505-Service down error when they submit their credit card information but forgot to enter their zip code, or some such.
So you have to wrap that statement in a try-block, and try it and see if it works, and if it doesn't you have this whole envelope, this whole burrito shell of code you have to write to handle the uninhabited case.
Now let's look at the monadic function binding again:
m >>= f >>= g >>= h >>= ... etc.
What happens if m is null, or the result of f m is ...
No. Wait. Monads don't have nulls. Monads are just monads, so the above binding ...
... okay, wait for it, ...
just.
works.
In fact, in the Kleisli category, it is guaranteed to work.
It. just. works.
For you, not a Java(script) programmer, you're like: "Well, duh, yeah, that's how it's supposed to be! 1 + 1 = 2, not 'error' or whatever."
You can say that, and expect that, in a pure mathematical domain (with monads implicit in the mathematics for you. You're welcome.) But a Java programmer, with any experience, any hard-won experience should be (rightly) awed.
"Wait. You mean, it just works? How is that possible?"
Yeah. How is that possible? That Java code just works?
It doesn't. But, when I translate the Kleisli category down into a Java implementation, and then lift every Java object into that cateogy, so it's not a Plain-old Java Object (POJO) any more, but now it's a monad in the Kleisli category, and operate on it as such from then on.
Well, then, it just works.
Some people look at my monadic harness and say I'm unnecessarily complicating things.
I beg to differ.
"But, geophf, I just want the start date; all this stuff just complicates things!"
But what do you want the start date ... for? If you just want the start date, then
pojo.getStartDate()
will get you there, and you're done.
But if you want to base any logic off it? Is your start date the start of a work-flow? So if the start date is null, your system, that you unit tested with start date values, now crashes in production and you have no idea why, because you unit tested it, and the code looks good.
But that one null wrecks everything.
I don't have any nulls. So I lift my start date up into a monad, and start my work flow, and my work flow works, and if the start date is null, then the start date isn't lifted, the functions fall through, because they're not working with a monad, and I just go onto the next thing.
And I coded zero lines of code around that.
Your try-catch blocks looking for nulls everywhere ...
Well, what's more complicated now? What code works in production, regardless of dirty or bad data?
Monads are a guarantee, and, as burritos go, they are quite tasty. Give them a try!
p.s.:
Now there's the whole conversation around monads that carry no value intentionally throughout the entire monad. So, for example, if the monad type is 'Quark' the the monadic inhabited types are up, down, strange, charmed, top and bottom, and their values are ... nothing at all: their instance is the value, it has no underlying value it carries (unless you want to get into the guts of spin and charge ...), its the particular monad instance, or the particular quark, we care about, not what's inside at all, something like monads as enumerated values, but ... eh. Monads-as-burritos is a limited view, it will trip you up, but for the most part it works. When you do get to the point of treating monads-as-monads, and not caring, anymore, about the underlying value, you can tap yourself on the shoulder with monadic-Nirvana. It's a really good feeling, and gives an entirely new understanding of monads and their utility. "Good to know," as it were, but monad-as-burrito works for the most part and is tasty, too, to boot.
p.s.:
Now there's the whole conversation around monads that carry no value intentionally throughout the entire monad. So, for example, if the monad type is 'Quark' the the monadic inhabited types are up, down, strange, charmed, top and bottom, and their values are ... nothing at all: their instance is the value, it has no underlying value it carries (unless you want to get into the guts of spin and charge ...), its the particular monad instance, or the particular quark, we care about, not what's inside at all, something like monads as enumerated values, but ... eh. Monads-as-burritos is a limited view, it will trip you up, but for the most part it works. When you do get to the point of treating monads-as-monads, and not caring, anymore, about the underlying value, you can tap yourself on the shoulder with monadic-Nirvana. It's a really good feeling, and gives an entirely new understanding of monads and their utility. "Good to know," as it were, but monad-as-burrito works for the most part and is tasty, too, to boot.
No comments:
Post a Comment