Categories
Programming

Monads by stealth

Common coding idiom: A window sometimes has a tooltip associated with it. A file object may be currently open, and therefore has a current offset, or it may be closed in which case it doesn’t. In both of these cases, maybe we have the extra data or maybe we don’t – it depends on the object’s state.

Mostly in C++, people use pointers for this. The window object contains a pointer to the tooltip object which is valid when the tooltip is relevant, and null the rest of the time. To me, this is a mediocre solution.

Before I go any further, I’d better explain that one of my strong beliefs is that you should be able to get a really good idea of how a class works just by looking at the data members (ie. the state) of the class. You shouldn’t really need to read the implementation. I find this a pretty useful test of code quality and code complexity. Classes which have been prematurely optimised will show up as having more data/state than they Really Need To. Classes which don’t have a single well-defined responsibility will often stand out because they have extra “I’m currenlty in this state” flags. Well-written classes will have distilled their state down to the absolute essentials, and every data item should be able to justify their inclusion.

Furthermore, it’s always a good idea to convey as much of your intent in your source code as possible. If an object owns another object, it is much better to use an auto_ptr than a raw pointer, since that’s their raison d’etre. Spare a thought for the guys who have to read your code six months later, and be as precise as possible with your data types. If the object owns the other object from birth until death, use a const auto_ptr, since that’s all that a const auto_ptr will allow. (Of course, this only works if everyone understands the implications of the idiom. But describing idioms once to people is much easier than having to document the hundreds of places in the code where the idioms use would’ve made things cleaner).

Anyway, back to the “maybe has it, maybe doesn’t” story. Most people use pointers for this. I called this mediocre because using a pointer doesn’t convey the “might be valid, might not” intent. I’ve seen people using a convention of suffixing pointers with 00 to indicate “may be null” – as in “if (window->tooltip00) {..}”. That’s a useful convention, but it’s not something which a compiler can check.

Another common approach to this problem is to define a Maybe (aka Optional) datatype. It’s a simple thing – a Maybe value either currently holds an integer or it’s empty. You can call a method (eg. isPresent() ) to see if it currently holds proper data or not, and call a get() method to retrieve the data.

This has some advantages over using a pointer. For a start, it’s pretty clear this this “maybe data” may be present or may not be present, and so that’s a big win in terms of communication. Also, it doesn’t force you to switch to handling data by-reference like pointers do.

But it’s still not perfect because there’s no way for the compiler to verify that your program only calls get() on maybe-values which have real data. Sure, you can add a runtime check but it would be much nicer if we can verify this property of our program at compile time.

One thing which strikes me is that every place where processing of a Maybe<> value occurs we end up with an if-statement to check whether or not it’s valid. That’s a shame, since it’s basically code duplication all over the shop. Is there a way we could push this duplicate conditional code inside the Maybe<> class itself? Can we take the two bodies of the conditional and pass them to the Maybe<> class to process?

At this point, we’re kinda thinking upside down. Rather than thinking of Maybe as a passive container, we’re going to think of it as the active partner. It knows what it’s internal state is, but it won’t let you at it directly. Instead, the only thing you can do with a Maybe is pass it a couple of function pointers called “do this when you have data” and “do this when you’re empty”. It’s a bit of a shame having to turn the conditional bodies into fully-fledged functions, but that’s pretty much the best we can do in C++. So now the Maybe<> class gains a new method which, being unimaginative, we could call “doStuff” – it just checks whether it has real data, and runs the appropriate argument (ie. a function). Now we’ve managed to factor out all these conditionals.

(Well, it’s a pretty limited idea when we’re using C++ because creating functions is fairly work-intensive. You have to figure out what data they need, and pass it to them. If we used a better language (like, maybe, ocaml!) – which allowed you to treat functions as flexibly as values, creating them on the fly, automatically capturing their context, and passing them around as easily as if they were integers – then it’d be a much more powerful notion).

So, this has been a refactoring of the Maybe<> class from a simple passive data container into a more active participant (albeit a refactoring which can’t be expressed particularly elegantly in C++). It was driven by a desire to more fully express our intent in the source code, and to ensure that we always deal with the two cases (data, and no-data) every time we process a Maybe<> value. But what we’ve done is interesting. We went from a situation where the Maybe’s state was externally visible, to a situation where it was safely wrapped up inside the Maybe object and could only be acted upon by the “special tongs” of the two function we passed to the Maybe<> object.

A lot of work for a little advantage? Nah, now you’re basically playing with a monad, and you never even noticed.

3 replies on “Monads by stealth”

I’m not familiar enough with the subject, but I’d like to note that a function is a detriment to clarity that you pay to buy abstraction. In this case, I see the tradeoff as misplaced, but then I’m a dinosaur.

Presumably what you want is not to abstract the program logic but to ensure it is well-formed. I would prefer too see that done with typing of expressions rather than functions, for example by adding an overloaded “maybe” keyword to C++. It’s use is made clear by the following example:

maybe int m;

maybe (m) {
// m is in scope as type int
} else {
// m is hidden from scope
}

I did say I was a dinosaur 😉

(I note that if you come straight to this page, you can’t easily see that I commented previously via a TrackBack, so…

“I commented previously in my own blog, which you can get to if you hit the TrackBack link at the foot of Andy’s article.”

Now on to this comment…)

I note that you can write code with structure much like Pavlos desires by having Maybe<X> implement an implicit cast to X that throws a MaybeIsInvalid exception when the object is invalid. Then you can write

Maybe<int> m;
try {
// use m as if it has type int
} catch MaybeIsInvalid {
// your alternative action
// (m remains in scope, but I’m not sure why it shouldn’t, Pavlos – you might want e.g. to assign to it.)
}

Comments are closed.