The Imperative Programming Monad

Up to this point I have not followed through on my promise to offend everyone in general, and in particular, functional-programming-in-imperative-languages advocates. Everything’s been pretty positive about functional programming.

This is where the tide is going to start to turn, but I’m going to lead into it a bit before getting really offensive.

“Programmable Semicolons”

Let’s talk about monads, and in particular, monads in their capacity as “programmable semi-colons”.

If you understand why monads are “programmable semicolons” it’s a reasonably good sign you have a good understanding of them. I don’t think it’s the best way to understand them from the beginning, but it’s a good development of an initial understanding. I am not jamming a full monad tutorial in here when I’ve already got one (see prior link), so I’m just going to assume that you’ve read that and conclude with why this makes sense.

result :: [(Integer, Integer)]
result = do
  x <- [1, 2, 3]
  y <- [4, 5, 6]
  return (x, y)

This operates “in the list monad”, or as I prefer to phrase it, “with the list’s implementation of the monad interface”. To imperative eyes it looks like this assigns a list to x and y and returns the two lists together, but this actually “rewrites the semicolon” to say something like “when taking values from a list through <-, instead of taking the list, feed the values one at a time to the value and gather a list of the results of running the rest of this function.” So first x is assigned 1, the rest of the function is run and gathered into a list, then x is assigned 2 and gathered into a list, then 3. This also recursively applies to the y line, so the end result is that we end up with 9 things:

[
  (1,4), (1,5), (1,6),
  (2,4), (2,5), (2,6),
  (3,4), (3,5), (3,6)
]

As I mentioned in my post about monad, this exact functionality is a bit of a parlor trick because it gets out of control exponentially quickly, but the general principle has a lot of utility to it and leads to all those wonderful monad operations you’ve heard of, especially the ones that go beyond some variant of “value container”, like software transactional memory, which “rewrites the semicolon” into being resumable transactions instead of statements that either succeed (do what the line says they do) or fail (throw some variation of exception and start walking up the stack for a handler), creating a “semicolon” that defines a “transaction” that can be restarted safely.

“Programmable semicolon” is a bit weird because we don’t expect our semicolons to affect the semantics of the lines of code this deeply, and I would certainly not push it as what monads “really are”, though. It’s more of a convenient way of thinking about how some particular types that implement the monad interface are used.

Now, imperative languages can be said to “lack monads”, or, you could say, imperative langauges are locked into the one “semicolon” that they ship with. However… consider the following realignment of your mental map.

An Example: Stream Processing

Let’s suppose you’ve got yourself a network application. It’s going to read in some newline-delimited JSON statements, run a couple of transforms over them, and wait until a statement matching a certain predicate occurs. Then it will return that statement and return.

There’s many ways to do this, but let’s take a favorite and do this as a pipeline. You can easily construct this as:

  1. A pipeline to take a newline-delimited value off the stream.
  2. A component to parse the JSON into a specific type.
  3. The transforms or filters you want to run as some set of transforms.
  4. A component to select out the target value.

And your overall pipeline library will probably have a “driver” of sorts to allow something like this selection operation at the end to return a value in some manner.

You can easily specify this pipeline in a monadic syntax style in Haskell. While you may lack the syntax support in your imperative language for such a thing, it is not hard to either grab a library to do it, or even to bash something out that allows operation on a pipeline like this.

I’m not about to tell you this is automatically “bad”, either. In some cases it may well be an optimal solution; what comes particularly to my mind is the case where the filters and transforms in step #3 are user-specified, meaning that at the time you are writing the code you don’t know what they are or how many there will be. At that point you’re going to have to represent these as some sort of defunctionalized, code as data structures that can be executed by an interpreter (another instance of the power of that general approach), so that you can construct the pipeline at runtime.

It would be nice if imperative languages offered the ability to specify such a pipeline in a monad.

Well… allow me to introduce…

The Imperative Programming Monad

Consider the psuedocode:

def pipeline(inFile, output):
    while !inFile.empty():
        nextMessageBytes = inFile.nextLine()
        message = parseMessage(nextMessageBytes)
        transformed = transformMessage(message)
        summary = summarize(transformed)
        if summary.Important:
            output.write(summary)

It isn’t that hard to squint and see this as something like:

pipeline inFile output = 
    while (not empty inFile) $ do
        nextMessageBytes <- nextLine inFile
        message <- parseMessage nextMessageBytes
        transformed <- transformMessage message
        summary <- summarize transformed
        if summary.Important then
            write output summary
        else
            ()
            

That is not Haskell. For one thing a Haskeller would probably have a nextLine >=> parseMessage >=> transformMessage >=> summarize in there. But that’s just a succintness argument rather than a code structure one, and I’ve talked already about the general futility of trying to emulate Haskell in imperative langauges at such a small scale so I won’t rehash that.

You could say that functional programming critiques imperative programming for having only the “imperative programming monad”, or if you prefer, the “sequencing monad”. This is a valid critique.

However, when what you want is the sequencing monad… there’s nothing wrong with using what is already present in your language.

And wanting sequencing is a rather common thing. Less common perhaps than an imperative programmer who has never tried out functional programming might recognize, but still fairly common. One might be able to mathematically visualize a transformation pipeline in such an abstract manner that the order of operations ceases to be of interest to the mathematical model, but here in the real world, where “causality” is a thing that we have to deal with no matter what hash it makes of our real mathematical models, it’s still pretty common.

When it is in fact what you need, there is no crime in using the built-in monad that imperative languages come with. In fact reaching for a “pipeline” library, if you are not going to use that library for any other purpose, such as concurrency control, distribution across multiple nodes, or using some external event bus, if all you’re doing is sequencing several operations… what you’ve got there isn’t “better than imperative code”, what you’ve got there is an inner platform. That’s the opposite of “better than imperative”.

Now that’s going to annoy some people.