Update: this is an April Fool's joke. It's the most incoherent, illogical thing I could think of. It's a bad idea, it wouldn't work, and it misses the point of everything.
Hungarian Monadic Notation: Call Me Maybe()
Update: This was an April Fool's joke. If you are interested in monads and are a C# or Java programmer, I strongly suggest this series of posts by Eric Lippert, which explains monads in a very pragmatic way.
If you follow the world of software development at all, you know that there has been a big uptick in discussion about "functional programming languages," a type of language that emphasizes immutability and composition of functions to achieve higher reliability and, arguably, higher productivity.
On the other hand, functional programming languages look like Klingon, but with math:
instance Monad[] where
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []
WTF, ammiright? (And for extra points, it's not a right-shift, it's "bind." Because arcana.)
The most laughable claim of the functionistas is that functional code is easy to comprehend and debug. Lemme' tell ya, bud, I've been a dozen lambdas deep trying to figure out behavior and this whole "functional code is stateless," is more full of crap than an Oklahoma feedlot at slaughter time.
Functional programs have every flutter of state that an imperative program has, just not in any convenient stackframe. Or -- and this is awesome to debug -- it's not yet in the stack frame -- what's in the stack frame is a bunch of goddamn function pointers and not-yet-enough parameters to call them. Sometimes the thing you're stuck on is a dozen lambdas down and it's building a goddamn function pointer that will calculate the parameters (Excuse me! I mean calculate exactly one parameter!) to another goddamn function pointer. Thank heavens you don't have to struggle with the mind-numbing complexity of an if
branch ("Oh my God! The code could go this way or it might go that way! What kind of cephalopodic mind could comprehend this tangle‽").
And don't get me started on compilation speed. I love this idea that the "compiler finds certain errors." What they don't say "And it does this in a mere several minutes. So don't worry about missing one of those show-stopping narrowing conversions! We'll take a look at it every goddamn time you want to fix a typo in a different source file."
But, nonetheless, there are a few things that the FP world does that actually make sense once you machete away all the obfuscatory mathematical rigor.
Maybe Something Functional Does Right
The biggest practical thing, on a day-to-day basis, is that functional code doesn't have to deal with null
values. If you have some complex object that may-or-may-not initialize properly (let's say a camera which may or may not be available on your phone), you are probably used to writing code that looks more or less like this:
var theCamera = AttemptCameraInitialization();
if(theCamera == null){
DealWithNoCamera();
}else{
var aPhoto = theCamera.TakePhoto();
if(aPhoto == null)
{
DealWithNoPhoto();
}
…etc…
}
Or maybe:
try{
var theCamera = AttemptCameraInitialization()
var aPhoto = theCamera.TakePhoto();
}catch(npe){
DealWithNullPointerException(npe);
}
And while the try-catch
style isn't nearly as wordy as the constant-error-checking style, it's a little harder to debug and good coding style says that one ought not to rely on exceptions to deal with common alternatives. Contrast this with what one might see in the functional world (exact names and syntax differ between functional languages, so I'll use Scala, whose Klingon-y parts can be swept under the rug, and can actually be comprehended by humans):
val theCamera = AttemptCameraInitialization();
val thePhoto = theCamera.TakePhoto();
And even if there was a problem during AttemptCameraInitialization()
, no exception would be thrown. Instead, theCamera
would be represented by a None
object and, when a function is called on a None
object, that None
object simply returns another None
.
So this may look like no big deal with a couple lines of code, but if you have an A
that has a B()
function that returns a B
that has a C()
function that returns a C
that…a Y
that has a Z()
function that returns a Z
, you can write code like:
var myZ = a.B().C().D().….X().Y().Z();
Which is much more readable than putting a bunch of checks-for-null between every method. And in this situation, you just are ploughing along nicely through your functions until one returns a None
, in which case every function subsequent to that returns a None
. (So how much fun is it when you've stepped your debugger up to that one line of code? But put that aside for now…)
So, talk to a functional guy about what's going on and BOOM he drops the "M" word. Of all the gatekeeper-words in the mystical arcana that guard the entrance to the D&D den where all the cool functional kids are hanging, none has the power of "Monad." It evens sounds like something that Gandalf would warn you about: "Beware the realm of endofunctors, for there, monoids become monads!"
Lemme' tell ya', I've been There and Back Again and monads are no big deal. They're just goddamned parameterized types with a handful of easy semantic rules. Seriously. That's it. The reason that they're confusing is that the semantics are so stupidly underwhelming that you keep thinking "Yeah, okay, but what else?" It's like the gate to your vast underground treasure-city being guarded by the word "friend," a fact which is conveniently documented on the transom.
Anything Monads Can Do, Inheritance Can Do Better
So how are we gonna' do this? Easy! Let's say we start with a couple classes that look like this:
class Camera
{
Photo TakePhoto() { … etc…}
}
class Photo
{
Bitmap GetImage() { … etc… }
Exposure GetExposure() { …etc… }
…etc…
}
All you have to do is define the interface to these objects, so that we can implement simple alternative "None" classes. But to help future maintenance programmers, we're going to use a little invention of mine called "Hungarian Monadic Notation" to indicate our intent:
interface MMaybeCamera
{
MMaybePhoto TakePhoto();
}
interface MMaybePhoto
{
Bitmap GetImage();
MMaybeExposure GetExposure();
}
The way this works is that we indicate that a class or interface has this monadic intention by prefixing it's name with an "M". But because there are a lot of different monad patterns, you specify which monad in particular you're talking about as the next part of the name. So in this case we have MMaybeCamera
indicating that we're looking at this particular pattern. Ditto for MMaybePhoto
but you could also have, like, MEitherPhoto
or MStateCamera
etc. The possibilities are endless! And it's super-easy to do and doesn't introduce any of those stupid compilation errors!
Now that you've used HMN to name your interfaces, you just change your original implementations to implement the HMN interface:
class Camera : MMaybeCamera{ … original code… }
class Photo : MMaybePhoto { …original… }
But in addition, you also implement the alternative "empty" objects:
class NoneCamera : MMaybeCamera
{
MMaybePhoto TakePhoto()
{
return new NonePhoto();
}
}
class NonePhoto : MMaybePhoto
{
MMaybeExposure()
{
return new NoneExposure();
}
Bitmap GetImage()
{
throw new MonadException("This is a none,son.");
}
}
Isn't that inspiringly clean code? When you call a function on a None{x}
class, it just returns another new None{y}
object! Or, when you need to break out a real class, you just throw a MonadException
. (The details of MonadException
are left as an exercise.)
Naturally, you kick this all off with a MMaybeCameraFactory
to initialize your original MMaybeCamera.
Future Work
Although Hungarian Monadic Notation and it's implementation patterns are agile best practices worthy of the enterprise, it's true that there's a certain amount of boilerplate code that's associated with implementing each of the functions in the base interface.
In a future post, we'll cover the answer:
Reflux: The Monadic Reflection Dependency Injection Framework