So simple, and yet this is a point that I think is rarely made clear enough in "monad explainers." For instance, they almost always talk about "the Maybe monad" -- but this is conflating two things: the Maybe data type, and the Monad instance defined on that type. Propagating "Nothing" is not inherent to the Maybe data type, it's just a convenient behavior to have.
Talking about "the List monad" is even more confusing for a newcomer. When they hear "the List monad implements a kind of nondeterminism," it sounds like nondeterminism is a property inherent to lists themselves -- but of course it is nothing of the sort. All Monad instances are, in a sense, arbitrary.
nitpick about this particular example: is there another lawful implementation of Monad for Maybe? i can't think of any, apart from the trivial
pure _ = Nothing
_ >>= _ = Nothing
pure >=> f == f (left identity)
pure a >>= f == f a
Just a >>= f = f a
Nothing >>= f = Just (_: B) -- we can achieve a B only via use of f, so
Nothing >>= f = Just (f (_: A)) -- now we are stuck, there are no values of A
pure a = Just a
Just a >>= f = f a
Nothing >>= f = Nothing
pure a >>= f ≡ f a
m >>= pure ≡ m
I’m sure most real-world Haskell programs are “incorrect” in some way.
That being said, the language design problem of how to support multiple different typeclass implementations for one type is actually trickier than you'd think. For example, it's easy to fall into the diamond problem if you don't have instance canonicity. If you're interested in how this problem can be solved in practice, check out this lovely paper about Modular Implicits in OCaml . The paper is quite accessible if you have some FP background.
For instance, not much would change about programming in Haskell if List was defined as an abstract type, so users couldn't see its constructor or interact with it in any way except through its functions.
In OOP languages, this is unambiguous because the function/interface implementation is explicitly defined as part of the type. In FP languages, this is a little less clear because the implementation can be defined anywhere (although, should really be either near the data declaration, or the typeclass declaration).
The "diagonal" monad join [[a, b, c, ...], [1, 2, 3, ...], [x, y, z, ...], ...] = [a, 2, z, ...] has return a = [a, a, a, ...], though I guess it's hard to define it in a way that's well-behaved for finite lists, so you might not consider it "a monad on lists".
The only exotic monad which was intuitive to me was GlobalFailure, which interprets lists as "Maybe NonEmpty": An empty list signifies failure and aborts the whole computation, while non-empty lists behave as normal.
Consider it as an alternative to the more common strategy of absolutising the modifications onto the leaves in a first tree walk, and then executing all the now-labelled leaves in arbitrary order in a second pass.