Monads for Java/C++ programmers
irekjozwiak.com
irekjozwiak.com
And I was kind of anticipating monads since I keep hearing that they just blow your mind and are hard to understand. So, I was happy to read this article and found it quite good actually :) Good job to the author.
Here's how I can summarize it: monads are a kind of pattern which "implements" ">>=" and "return" as a Int implements "eq" or "ord". The job of >>= is to act a little bit like the Null Pointer pattern in the OO world. For instance, if everything is ok, it returns the good value else it throws an exception or return an error value. However, to use the >>= function, you need to encapsulate your first value. Depending of monads, that "encapsulated" value may be different. In this article, we were working with the Maybe type, however, monads can be of any type. So, the "return" function takes care of encapsulating the value in the right type so that the >>= function can use it.
Anyhow, that's what I understood ;-) And I'll continue to read learnyouhaskell.com
I tend to write these off with "yeah, in my 20s and early 30s whilst in graduate Comp. Sci. schooling I enjoyed all this esoteric crap, too". :-)
The simplest possible semicolon is one that lets you do anything, including interact with the outside environment. That's the IO monad. But you could also have a semicolon that limits your side-effects to one particular area of memory - that's the State monad. Or you could have one that doesn't execute any of the following statements if an error occurs - that's an exception monad. Or you could have one that attempts to atomically execute a bunch of statements that may read & write from a block of memory, but if that memory is changed by another thread, the effects are all rolled back and tried again. That's the STM (software transactional memory) approach to concurrency. Or you could have one that sends a webpage to the user's browser with an HTML representation of the state, waits for a response, and then executes the rest of the statements. That's the approach taken by WASH, one of the Haskell web frameworks.
It's a powerful tool, because it's basically rewriting the fundamentals of how the language works. And because of that, very few people ever have a reason to create a new monad type, which is why examples tend to fall flat. It also interacts heavily with other Haskell features, eg. you don't get this correspondence between monads and statements without Haskell's do-notation, and you can't use them to affect control flow without lazy evaluation, and they become very difficult to reason about without a static type system.
In any case, monads are just what he said, a design pattern. I dislike the over-application of patterns as much as anyone does, but recognizing common abstractions is one of the parts of programming that separates the men from the boys.
Monads just get a lot of attention because the pattern is super useful in certain situations, like pure functional programming.
Anyway, in the face of Haskell and Lisp, the differences between java an C++ don't look that great.
// Haskell typeclass equivalent is >>=
// which has a signature of:
// m a -> (a -> m b) -> m b
// which in our case, is the more specific:
// Cont a b -> (b -> Cont a c) -> Cont a c
function bind(x, y) {
return function(k, end) {
return x(function(result) { return y(result)(k, end); }, end);
}
}
// 'yes' can also serve as identity for this monad. Haskell monad typeclass
// equivalent is 'return', not to be confused with JavaScript return. if
// this were an applicative functor, we would call it 'pure' instead, even
// though it does the same thing.
// b -> Cont a b
function yes(x) {
return function(k, end) { return k(x); };
}
// bail out.
// a -> Cont a b
function no(x) {
return function(k, end) { return end(x); };
}
// look up a user ID from a name string, in the context of a continuation
// monad. our type signature for this in Haskell would be:
// String -> Cont String UserId
// or something.
// 'UserId' would be a newtype wrapper around Integer, to prevent
// accidentally conflating it with account balance or another numeric type.
function userid_from_name(person_name) {
switch (person_name) {
case "Irek": return yes(1); // we have three user names in our system
case "John": return yes(2);
case "Alex": return yes(3);
case "Nick": return yes(1); // Nick is on the same acct as Irek
default: return no("No account associated with name " + person_name);
}
}
// UserId -> Cont String Balance
// 'Balance' would also be a wrapper around Integer type
function balance_from_userid(userid) {
switch (userid) {
case 1: return yes(1000000); // some amounts for a couple of accounts
case 2: return yes(75000);
default: return no("No balance associated with account #" + userid);
}
}
// Balance -> Cont String Balance
// we could do something fancier here if we liked, like pass the difference
// between the minimum required balance for a loan and the actual balance.
function balance_qualifies_for_loan(balance) {
if (balance > 200000) return yes(balance);
else return no("Insufficient funds for loan, current balance is " + balance);
}
// tada. put it all together.
// String -> String
function name_qualifies_for_loan(person_name) {
return bind(bind(userid_from_name(person_name), balance_from_userid),
balance_qualifies_for_loan)(
function(x) { return "This person qualifies for a loan. Their \
account has a balance of: " + x; },
function(x) { return "Do not issue loan, reason given: " + x; }
);
}
Test some output: > name_qualifies_for_loan("Irek");
"This person qualifies for a loan. Their account has a balance of:
1000000"
> name_qualifies_for_loan("John");
"Do not issue loan, reason given: Insufficient funds for loan, current
balance is 75000"
> name_qualifies_for_loan("Alex");
"Do not issue loan, reason given: No balance associated with account #3"
> name_qualifies_for_loan("Nick");
"This person qualifies for a loan. Their account has a balance of:
1000000"
> name_qualifies_for_loan("Foo");
"Do not issue loan, reason given: No account associated with name Foo"
Pure functional. Holler at a player.Have a nice life. Inside your alone monad.
The call becomes simpler: balance_qualifies_for_loan(balance_from_userid(userid_from_name(person_name))), as well as functions which just return the value or throw.
I'm going to go eat a burrito now.
Implementing exceptions with the continuation monad is straightforward. As you can see, that's sort of how it's used here. You can also recreate the State monad, Maybe, and others, deriving each from the continuation monad. I don't recommend this for actual use in JS, because JS lacks tail call optimization, which means any serious use of monads will cause stack overflows straight away.
But it gets crazier: you can implement the continuation monad using JavaScript exceptions. And JavaScript exceptions do cause the stack to reset (with a performance penalty). So if you derive a continuation monad in JS from its built-in exceptions mechanism, you can implement the other monads without breaking the stack.