Monads and Macros

There are two techniques in software development that have an almost gnostic mystique about them: monads and macros.

Pride and pragmatism

As with everything people do, monads and macros are used with mixed motives, for pride and for pragmatism.

As for pride, monads and macros have just the right barrier to entry: high enough to keep out most programmers, but not so high as to be unsurmountable with a reasonable amount of effort. They’re challenging to learn, but not so challenging that you can’t show off what you’ve learned to a wide audience.

As for pragmatism, both monads and macros can be powerful in the right setting. They’re both a form of information hiding.

Monads let you concentrate on functions by providing a sort of side channel for information associated with those functions. For example, you may have a drawing program and want to compose a rotation and stretching. These may be ostensibly pure functions, but they also effect a log of operations that lets you undo actions. It would be burdensome to consider this log as an explicit argument passed into and returned from every operation. so you might keep this log information in a monad.

Macros let you hide the fact that your programming language doesn’t have features that you would like. Operator overloading is an example of adding the appearance of a new feature to a programming language. Macros take this much further, for better or for worse. If you think operator overloading is good because of its potential to clarify code, you’ll like macros. If you think operator overload is bad because of its potential for misuse, you definitely won’t like macros.

Mutually exclusive

Few people are excited about both monads and macros; only one person that I know comes to mind.

Monads and macros appeal to opposite urges: the urge to impose rules and the urge to get around rules. There is a time for both, a time to build structure and a time to tear structure down.

Monads are most popular in Haskell, and macros in Lisp. These are very different languages and their communities have very different values [1].

The ideal Haskell program has such rigid structure that only correct code will compile. The ideal Lisp program is so flexible that it is essentially a custom programming language.

A Haskell programmer would like to say that a program is easy to reason about because of its mathematical structure. A Lisp programmer would like to say a program is easy to read because it maps well onto the problem at hand.

Lisp enthusiast Doug Hoyte says in his book Let Over Lambda

As we now know, nobody truly understands macros.

A Haskell programmer would find this horrifying, but a Lisp programmer would consider it an acceptable price to pay or even something fun to explore.

Related posts

[1] Here I’m referring to archetypes, generalizations but not exaggerations. Of course no language community is monolithic, and an individual programmer will have different approaches to different tasks. But as a sweeping generalization, Haskell programmers value structure and Lisp programmers value flexibility.

7 thoughts on “Monads and Macros

  1. I love learning things from young programmers. One of them replaced an elaborate monadic system I had built in Python (using the o-slash library) with good-ol’-decorators! She obviously figured out what I was doing and replaced it with something she found more understandable.

    Henceforth, I haven’t found anything that I want to do with monads in Python that I cannot do more idiomatically with decorators! Decorators are the tortilla wrapping the burrito!

  2. Nice analysis! Something I’d love to see is a study of programming language communities focusing on (1) what they value and (2) what kinds of problems they like to work on. My hope is that such a study would illustrate to everyone that the world of software is much too big for any one point of view to be the “best” one, and that different languages are complementary rather than in competition. The conclusion would then be that we need different language communities to cooperate, in order to make large-spectrum software systems possible.

    Having disqualified myself from conducting such a story by having written my conclusion, I can now only hope that someone will take it up to prove me wrong!

  3. A macromonad is a bacterium of the genus Macromonas. I spent a while trying to figure out how that gray thing in the picture was a really big bacterium.

  4. I think there’s a middle of the road where both are very exciting concepts.

    Operator overloading IS a great way to clarify code. BUT only when there are rules in which those operators are “okay”.

    For example, overloading the + operator is okay as long as it acts like addition over its operands. Like adding two money objects. For this, some way of defining and verifying identity, associativity, commutativity would be helpful.

    For this reason, it looks like Haskell monads and Lisp macros are different facets of the same thing. With regards to the latter, it seems Lisp achieves this power, conceptually, using a particular kind of monad – the list monad parameterised with a union type of lists and atoms. That means you can flat map syntax in the same way Haskel monads allow you to flat map all kinds of impure computation (the programmable semi-colon).

    What would be interesting to see would be a language that combines these two ideas, which has both the syntactic flexibility of macros with the type-safe rigour of monads.

  5. Misleading in the details, but monads vs macros do characterize Haskell vs Lisp.

    What you get wrong with monads is that monads are, functionally speaking, very much like macros in that they present a way to reduce the quantity of code by automating processes. The difference is that monads are tightly bound up within the type system–their definition is based on being able to add and combine layers of generics– whereas macros are strictly more powerful by being able to codewalk.

    The essential difference comes down to, Haskellers are afraid of their coding capabilities or the code of their community members; they’re the type that’s well-insured and take precautions before every vacation. Quite a few Haskellers claim that they wouldn’t have been able to make it in the software industry without having studied Haskell–their ability to handle mutable state either was never there in the first place, or atrophied.

    Lispers, on the other hand, are looking for more expressivity and power above all and generally radiate confidence; I’m told that there’s a few Clojurians who don’t write tests and still produce high-quality code.

    Haskellers and Lispers tend both to be very smart people, but the measure of a Lisp programmer can be seen in the quality of their code; of a Haskeller, the number of libraries built, abstractions mastered, and papers published.

    Or, in other words, a Lisper’s idea of a superpower would be the ability to put out tested and featureful code in the minimum amount of time. A Haskeller’s idea of a superpower would be to be able to program with the minimum amount of time spent debugging and writing tests. A Lisper is a fixed-wing pilot, exhilarating in the skies, while a Haskeller is a helicopter pilot, worried about having to actually use the autorotation technique they’ve practiced so many times.

  6. Just as an illustration (and Snoyman, author of Begin Rust, Yesod web library, conduit streaming library, and much more) in his blogposts often says stuff like “I’m not smart enough”, or that Haskellers don’t think they’re smart enough.

    https://www.snoyman.com/blog/2016/11/haskell-for-dummies/

    This is the real difference between Lisps and Haskell. Lispers are the real rocket scientists, who can come up with incredibly elegant code that’s often very correct. Haskellers are closer to MDs; the corpus of study is immense, but it’s more hard work than anything else. And what they get in return is a combination of elegance and safety.

    I personally like to quip that Haskell is Java with a Ph.D, that’s all; it’s focused on a combination of power, performance, and safety, but unlike Lisps, it won’t go all the way for power or elegance.

    The problem for us in the Haskell community is that we haven’t figured out how to change Haskell from Java with a Ph.D to Java with a M.S, much less just plain “much better functional Java”—we are really bad at teaching Haskell, but we are working on it. Maybe, one day, Haskell will be everyone’s most hated language, because it’ll be used in the same applications that Java is used today, and all the warts in the language (unpredictable performance due to laziness, terrible record types, although that’s improving, effects libraries that can’t be used due to terrible performance, terrible compilation times if we opt for metaprogramming solutions) are all anyone ever thinks about.

  7. I have been using Mathematica since 1981, when I got it personally from Stephen Wolfram at Caltech. It was his personal project called SMP back then, and not a commercial product until 1987. I needed it for verifying formulas for GPS clock correction. I quickly realized that it is a Lisp-like language that is MOSTLY MACROS, as in rewriting rules. I’ve used it ever since as an executable design language. I often design C and Python code in Mathematica before coding it up in C or Python. Mathematica has proper functions, but they’re rarely necessary and definitely downplayed. Things that look like functions, e.g., f[x_] := x * x, are really macros.

    I’ve also used Lisp (Gambit Scheme just today, and several big Clojure projects in the last few months). I rarely write macros in Lisp. Not sure exactly why, but I end up having to look up “define-syntax” and “syntax-rules” and it seems not worth the trouble. Macros are a little easier for me to write in Clojure than in Scheme, not sure exactly why. But they’re a joy in Mathematica!

    In all cases, they’re tricky to debug. There are various macro expander helper gizmos in the debuggers, but it seems more difficult to get them to work right than it it to just write an ECHO macro that prints and returns its arguments. It’s the one macro I can’t live without.

    I spent a lot of time understanding and exploiting moand-likes in languages other than Haskell. I learned a lot, had fun, did some cool things, but end up not really needing them, especially when I want someone else to understand my code. And I definitely gave up at lenses and comonads.

    There are other cool things similar to monads in difficulty, where I end up feeling like they might be more trouble than their worth. Delimited continuations and async/await come to mind. They’re great for brain-benders, and I’m not done with them, but I rarely use them in production.

Comments are closed.