Sounds a bit aggressive, doesn’t it? Well, let me explain. Monads are fine until you play with them in Haskell. But don’t bring them to Clojure. They are completely foreign beings to Clojure ecosystem. Besides the technical issues, there is a problem with sharing monads across the team. Read the following, and I hope you’ll get into what I’m worried about.

Nobody uses monads in libraries

So, why you’d better not to ship monads into Clojure code? First, they are used quite rarely in production. Let me open my recent project which is just another two-in-one boring stuff. An HTTP server plus a single page application. I’ve got plenty of dependencies here in my project.clj file. There are about 40 of them just to cover basic stuff. Database, oAuth, UI… I don’t want to dump the whole list here. Check this gist for the reference.

The full tree of dependencies expands into multiple screens. Their children libraries, and their children, and so forth. There are about 100 packages in total.

Now guess how many of them use monads? You know that, it’s zero. None of the libraries needs monads to bear their functionality. I’d like to stress that. Some of them provide a really tough stuff: database access, template system, HTTP communication, etc. Yet they work without monadic operations. Why? Just because Clojure is powerful enough. It brings everything you need for convenient development. The authors of those libraries didn’t use monads not because they are not aware of them but because they just don’t need them.

I don’t believe you are experiencing a trouble which is much more series then something mentioned above and thus craves for monads. There is definitely a plain way for doing this.

Railway Programming is a toy

Moving on, most of the people who are using monads told me, they are fascinated with the idea of Railway Programming. I believe you are aware of this site and their mental model in general. Several years ago I fell into it completely thinking I’ve found the ultimate solution. It didn’t work, I regret to say.

The problem with it is, it’s just a mock. A toy model which looks nice on the screen. At first glance, it reminds a holy grail that you’ve been looking for years and got it finally. So simple: just cut your code into a set of same looking chunks and then build a pipeline.

I’ve been praying for that picture for months.

Suddenly, it becomes clumsy and tough once you try to fit it into a real project. You’ve got to write more code. As I mentioned, none of the libraries really use it. All the functions should be wrapped to return monadic values rather than plain ones.

The standard let, cond, when, and dozens of useful core functions don’t work with monadic values. You’ll lose a huge stuff for no reason. Instead, you’ll have to implement monadic versions of let, if, etc. Then test them, write documentation. Why? It’s clear to me, the more code you’ve brought on board, the less the project is maintainable.

We are already there

The Railway Programming proposes a happy path where each step leads either to the next happy step or to the negative outcome. This is fine, but we’ve already got the same stuff, indeed. These are exceptions. They act the same way.

Take a look at the standard Railway example. They query the database, send email, write a file, etc. Surely, each of these steps is dangerous, whoever argues. But I don’t see a reason for why not to wrap the whole code with a simple try-catch statement? It acts exactly the same. For example, when an error occurs on the third step which is sending email, the entire pipeline terminates so we end up in the catch clause.

Here, we’ve got an exception instance with most of the data we need. What to do next is up to us. We can log the error and fail silently. Or rise another exception with a high-detailed message plus the source exception attached for a cause. That’s is sufficient for maintaining the business logic.

The main reason people turn to monads is, they’ve got lack of knowledge of using exceptions. There is no common rule for handling exceptions and thus it might be tricky sometimes. Some of them must be caught right here, others should flow up. For example, when iterating through the list, I wouldn’t like to stop the whole program once failing on a certain item. I just log the detailed message and go on. Or if I found a user doesn’t have enough permission for that endpoint, I throw an exception so it called by the top-level middleware. It does know how to turn in into a proper HTTP response.

I know this is just an extended version of the GOTO operator. It looks completely imperative but I don’t care. This is a business problem, not a Haskell tutorial.

Monads are built upon strict types

In fact, I don’t see any reason for using monads with a language of dynamic typing system. Don’t see at all. In Clojure, we’ve got nice collections for storing either data or a nil value. Since nil is treated as an empty collection and we mostly process collections in Clojure, there is no reason to worry about whether it was a map or just nil.

When I doubt if a value I’ve got might be nil, I just wrap the top-level form with (when value ...) which prevents me from dealing with nil. In addition, it returns nil and thus is compatible with threading macros as well.

The very need for monads comes when you’ve got an honest typing system. An honest system is such a one that doesn’t allow to pass empty values (nil, NULL, None, etc) for an arbitrary object like one does in Java. When you are out of this trick, you’ve got to compose a complex type with a bunch of special operations upon it.

A good example might a language with a strict typing system and algebraic types, say Scala. They haven’t got a Nil type at all. You cannot just pass an dummy value instead of a DateTime parameter. Instead, there is an Option<T> type which is a composition of either an instance of <T> class or a None value. This is only because of their type system’s limitations.

But this is not our case! In Clojure, we are not suffering from such things. It’s much easier to check the value for its completeness with the standard if form rather than wrapping it into a monad. You overcomplicate things, and that’s the problem.

By the way, talking about complexity. In Clojure, we don’t like complex things, we’d rather take simple ones. The entire language is about simplicity, you know. You either have got some data represented with a map or nil. That is simple. That is something you can always check within REPL out from your editor. Instead, a monad is a wrapper what obscures the original value and forces me to recover it with special macro. That’s too complex.

Leave other languages alone

That situation with monads in Clojure reminds me something from Python. Since it has map and reduce functions as well as flexible syntax, one believes it’s possible to program with Python in a functional way. The Internet is full of tutorials about higher order functions, functors and even monads. My shame, I used to write my own “functional” library for Python with all that stuff. It was an mess stiched from chunks of Scala and Clojure. I was really proud of it being sure this is really a great stuff. So do other authors, I suppose.

One minor detail is, nobody uses those “functional” libraries. I’ve seen a lot of Python code before switching to Clojure. Let me assure you, a functional way in Python is a myth. There is no a chance to deliver a Django project in functional terms. Put map and reduce here and there, it won’t affect the whole picture. After all, the very language is completely imperative so you end up with changing objects through their lifetime. This is not because Python sucks, no. I love it. It’s just a matter of different design.

Clojure also has got other design. It doesn’t know anything about monads. Talking in a more broad way, the idea of fetching foreign ideas from other languages doesn’t work. If a language is entirely imperative, functional way won’t apply here. When it has got dynamic types, the idea of adding strict type checks manually neither applies. Otherwise, you get something that we call Chimera. A strange mix of foreign elements that do not match each other. This is subject to break.

What would you do with the team?

One other note is, think for a while about not yourself but the whole team. Look, not everyone is as smart as you. We know you’ve been drilling Haskell by nights but know what? Nobody cares. You’ve been hired to solve business problems, not to tickle your arrogance.

With monads, we’ve got to teach the team first. Some of my friends are really great developers yet they know nothing about monads. I don’t see any reason to violate their brains. For what? To force them to know monads? They are already capable of solving any possible business problem. Live them alone.

I’m not against one-man-band use cases when you are the only one person responsible for everything. Use monads, monoids, do what you want. Yet it’s still an amateur way. You won’t go far as a single developer.

Wrapping up

Using monads is fine when you’ve got a language which is monad-friendly. They should be a part of the language’s design. Haskell or Scala would be a great choice for that.

Railway programming looks fine right until you put that gizmo into a real project. Dull exceptions would be enough.

Don’t borrow too much from other languages. What has been in Haskell should be left behind. It’s Clojure time! Shifting your mind between paradigms is really fun. But not sticking around the only “truthful” way!