I'm going to use a language-agnostic description of monads like this, first describing monoids:
A monoid is (roughly) a set of functions that take some type as a parameter and return the same type.
A monad is (roughly) a set of functions that take a wrapper type as a parameter and returns the same wrapper type.
Note those are descriptions, not definitions. Feel free to attack that description!
So in an OO language, a monad permits operation compositions like:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Note that the monad defines and controls the semantics of those operations, rather than the contained class.
Traditionally, in an OO language we'd use a class hierarchy & inheritance to provide those semantics. So we'd have a Bird
class with methods takeOff()
, flyAround()
and land()
, and Duck would inherit those.
But then we get into trouble with flightless birds, because penguin.takeOff()
fails. We have to resort to Exception throwing and handling.
Also, once we say that Penguin is a Bird
, we run into problems with multiple inheritance, for example if we also have a hierarchy of Swimmer
.
Essentially we're trying to put classes into categories (with apologies to the Category Theory guys), and define semantics by category rather than in individual classes. But monads seem like a much clearer mechanism for doing that than hierarchies.
So in this case, we'd have a Flier<T>
monad like the example above:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
...and we would never instantiate a Flier<Penguin>
. We could even use static typing to prevent that from happening, maybe with a marker interface. Or runtime capability-checking to bail out. But really, a programmer should never put a Penguin into Flier, in the same sense they should never divide by zero.
Also, it's more generally applicable. A flier doesn't have to be a Bird. For example Flier<Pterodactyl>
, or Flier<Squirrel>
, without changing the semantics of those individual types.
Once we classify semantics by composable functions on a container -- instead of with type hierarchies -- it resolves the old problems with classes that "kind of do, kind of don't" fit into a particular hierarchy. It also easily & clearly allows multiple semantics for a class, like Flier<Duck>
as well as Swimmer<Duck>
. It seems like we've been struggling with a impedance mismatch by classifying behavior with class hierarchies. Monads handle it elegantly.
So my question is, in the same way that we've come to favor composition over inheritance, does it also make sense to favor monads over inheritance?
(BTW I wasn't sure if this should be here or in Comp Sci, but this seems more like a practical modelling problem. But maybe it's better over there.)