Categorisation of monads

Most of the monads I can think of belong in one or both of these categories:

State

Can be modelled as a state monad

  • Reader
  • Writer
  • State
  • ST
  • STM
  • function (((->) r))
  • Identity (state with no state)

Error

Sequencing with early exit

  • Maybe
  • Either
  • ErrorT

Both

  • IO (with the real world as state)
  • parser combinators

The only (useful) exception I can think of, whose functionality is clearly distinct from both these categories, is List (and Cont, which is merely a generalization of Monad itself).

Are there any other monads, that can’t be modelled exclusively by either category, that I have missed?

Logic is another special monad related to Cont and lists.

I would classify the errors under “Non-determinism” together with List, because early exit is the same as an empty list of branches.

I see your point. By error, I mean ‘just perform regular pure (or stateful) computation, unless stopped by some exceptional condition,’ i.e. only carrying a single (optional) value. List does more than that, in that it applies computation to multiple items, so it can’t be ‘classed’ with the error monads, under my definition.

My objective is to provide a perspective on monads from a practical (maybe application) programmer’s perspective. I’m musing that maybe monads are over-abstracted, and it’s possible to define an easier-to-reason-about abstraction based on the concepts of state and early exit for practical programming.

For me personally, I don’t even find the list monad useful - i find it obfuscates more than writing the code explicitly, and doesn’t really give any helpful intuitions. I’d be curious to hear if someone has a different perspective.

This reminds me a little bit about the division between data and control functors in Linear Haskell.

That’s a good insight. I’ll have to think long and hard about that.

I’d argue that monad transformers are really useful but also very clunky without MTL/some effect system, either of which need abstract monads. Being able to mix state+non-determinism or streaming+async+resource management seems a lot harder without abstract monads.

For extra monad types: There is a (maybe a bit underused) pattern of profunctor monads that can do bidirectional things, basically a question-answer free monad

data QA query answer answer' = Done answer' | Ask query (answer -> QA query answer answer') | ...

This is useful if you need bidirectional things like parse/pretty print, query/update, etc. The refinery library uses it, but I’m pretty sure I’ve seen it in some serialization libraries as well.

There are also variants on a bunch of monads like adding codensity on top or tricks like ‘a smart view in datatypes’ which can really help performance and for which an abstract view on monads can help.