20

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.)

sea-rob
  • 6,841
  • 1
  • 24
  • 47
  • 1
    Not sure I understand how it works: a squirrel and a duck don't fly the same way - so the "fly action" needs to be implemented in those classes... And the flier needs a method to make the squirrel and the duck fly... Maybe in a common Flier interface... Oops wait a minute... Did I miss something? – assylias Apr 03 '14 at 00:30
  • Interfaces are different than class inheritance, because interfaces define capabilities while functional inheritance defines the actual behavior. Even in "composition over inheritance", defining interfaces is still an important mechanism (e.g. polymorphism). Interfaces don't run into the same multiple inheritance problems. Plus, each flier could provide (through an interface & polymorphism) capability properties like "getFlightSpeed()" or "getManuverability()" for the container to use. – sea-rob Apr 03 '14 at 00:36
  • 3
    Are you trying to ask whether using parametric polymorphism is always a viable alternative to subtype polymorphism? – ChaosPandion Apr 03 '14 at 00:56
  • yup, with the wrinkle of adding composable functions that preserve semantics. Parameterized container types have been around for a long time, but by themselves they don't strike me as being a complete answer. So that's why I'm wondering if the monad pattern has a more fundamental role to play. – sea-rob Apr 03 '14 at 02:35
  • I suspect this might be type classes vs classes not monads vs classes? otherwise the answer is surely no, as there are type classes/classes that can't meet the monad laws. – jk. Apr 03 '14 at 14:48
  • 6
    I don't understand your description of monoids and monads. The key property of monoids is that it involves an *associative binary operation* (think floating-point addition, integer multiplication, or string concatenation). A monad is an abstraction that supports *sequencing* various (possibly dependent) computations in some order. – Rufflewind Apr 03 '14 at 15:01
  • @Rufflewind thank you. Does the binary restriction apply to monoids in category theory? But that's a good point; I'll have to rethink. But it seems like the pattern above *is* an abstraction of sequencing (or chaining) operations. From the examples I've seen, monads don't naturally enforce a specific order (although they *could* as a part of their semantics... I've been thinking about playing with a state machine using that pattern). But the pattern does support sequencing... I left out the part about late binding because that was getting too long, so I just used early binding. – sea-rob Apr 03 '14 at 15:30
  • @jk maybe "type class that supports operation chaining" ? – sea-rob Apr 03 '14 at 15:35
  • @RobY Yes, monoidal categories have an associative "binary operation" too (correct term would be *bifunctor*). Monads are sequential because the `bind` operation (`>>=`) is asymmetric: later computations can depend on earlier computations but not the other way around. – Rufflewind Apr 03 '14 at 19:27
  • BTW, as you noted (but, sadly, removed, :( ) there **are** similarities between Monad,return,join and Monoid,id,append. +1 for noticing that! –  Apr 05 '14 at 23:45
  • I've seen monoid used as a description of a class with functions that map itself onto itself, but from the comments, it sounds like it really needs two things to actually be a monoid: a set of binary functions that take and return the same class: e.g. T t.do(T), associativity, and a "unit" function. So the next time I see someone claim that such-and-such is a monoid, I'll take that in the same sense that people say their service is "REST" ;) – sea-rob Apr 06 '14 at 02:46
  • @Matt Fenwick I can't really take credit for that -- I picked that up from a talk on you tube. But the speaker did make an interesting point that monad is a generalization of monoid that makes it more applicable. Plus, people do throw around the "monoid" term pretty loosely. I've heard that the Haskell folks worked hard over a period of time (and several versions) to get monad just right, so taken together, and my experiences with poor mappings of monad into OO, it points to something of deeper value than just another design pattern. – sea-rob Apr 06 '14 at 02:49
  • Nitpick: I think `Flier` would make more sense as an example because it flies like a bird, but is in fact a mammal. –  Apr 09 '14 at 20:42

2 Answers2

15

The short answer is no, monads are not an alternative to inheritance hierarchies (also known as subtype polymorphism). You seem to be describing parametric polymorphism, which monads make use of but are not the only thing to do so.

As far as I understand them, monads have essentially nothing to do with inheritance. I would say that the two things are more-or-less orthogonal: they're intended to address different problems, and so:

  1. They can be used synergistically in at least two senses:
    • check out the Typeclassopedia, which covers many of Haskell's type classes. You will notice that there are inheritance-like relationships between them. For instance, Monad is descended from Applicative which is itself descended from Functor.
    • data types that are instances of Monads can participate in class hierarchies. Remember, Monad is more like an interface -- implementing it for a given type tells you some things about the data type, but not everything.
  2. Trying to use one to do the other will be difficult and ugly.

Finally, although this is tangential to your question, you may be interested to learn that monads do have some incredibly powerful ways to compose; read up on monad transformers to find out more. However, this is still an active area of research because we (and by we, I mean people 100000x smarter than me) haven't figured out great ways to compose monads, and it seems like some monads don't compose arbitrarily.


Now to nit-pick your question (sorry, I intend for this to be helpful, and not to make you feel bad): I feel there are many questionable premises which I will attempt to cast some light on.

  1. A monad is a set of functions that take a container type as a parameter and returns the same container type.

    No, this is Monad in Haskell: a parameterized type m a with an implementation of return :: a -> m a and (>>=) :: m a -> (a -> m b) -> m b, satisfying the following laws:

    return a >>= k  ==  k a
    m >>= return  ==  m
    m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h
    

    There are some instances of Monad which are not containers ((->) b), and there are some containers which are not (and can not be made) instances of Monad (Set, because of the type class constraint). So the "container" intuition is a poor one. See this for more examples.

  2. So in an OO language, a monad permits operation compositions like:

      Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
    

    No, not at all. That example does not require a Monad. All it requires is functions with matching input and output types. Here's another way to write it which emphasizes that it's just function application:

    Flier<Duck> m = land(flyAround(takeOff(new Flier<Duck>(duck))));
    

    I believe this is a pattern known as a "fluent interface" or "method chaining" (but I'm not sure).

  3. Note that the monad defines and controls the semantics of those operations, rather than the contained class.

    Data types that are also monads can (and nearly always do!) have operations that are unrelated to monads. Here's a Haskell example composed of three functions on [] which don't have anything to do with monads: [] "defines and controls the semantics of the operation" and the "contained class" doesn't, but that's not sufficient to make a monad:

    \predicate -> length . filter predicate . reverse
    
  4. You have correctly noted that there are problems with using class hierarchies to model things. However, your examples don't present any evidence that monads can:

    • Do a good job at that stuff that inheritance is good at
    • Do a good job at that stuff that inheritance is bad at
  • 3
    Thank you! Lots for me to process. I don't feel bad -- I greatly appreciate the insight. I would feel worse carrying around the bad ideas. :) (Goes to the whole point of stackexchange!) – sea-rob Apr 06 '14 at 00:29
  • 1
    @RobY You're welcome! By the way, if you haven't heard of it before, I recommend [LYAH](http://learnyouahaskell.com/) as it's a great source for learning monads (and Haskell!) because it has tons of examples (and I feel that doing tons of examples is the best way to tackle monads). –  Apr 06 '14 at 02:17
  • There's` lots here; I don't want to swamp the comments, but a few comments: #2 `land(flyAround(takeOff(new Flier(duck))))` doesn't work (at least in OO) because that construction requires breaking encapsulation to get at the details of Flier. By chaining opertions on the class, the details of Flier remain hidden, and it can preserve its semantics. That's similar to the reason that in Haskell a monad binds is `(a, M b)` and not `(M a, M b)` so that the monad doesn't have to expose its state to the "action" function. – sea-rob Apr 06 '14 at 02:54
  • #1, unfortunately I'm *trying* to blur the strict defintion of Monad in Haskell, because mapping anything to Haskell has a big problem: function composition, including composition on *constructors*, that you can't do easily in a pedestrian language like Java. So `unit` becomes (mostly) constructor on the contained type, and `bind` becomes (mostly) an implied *compile time* operation (i.e. early binding) that ties the "action" functions to the class. If you have first-class functions, or a Function> class, then a `bind` method can do late binding, but I'll take that abuse next. ;) – sea-rob Apr 06 '14 at 03:03
  • #3 agree, and that's the beauty of it. If `Flier` controls the semantics of flight, then it can expose lots of data and operations that maintain flight semantics, while the "monad"-specific semantics are really just about making it chainable and encapsulated. Those concerns might not (and with the ones I've been using, aren't) concerns of the class inside the monad: e.g. `Resource` has an httpStatus property, but String doesn't. – sea-rob Apr 06 '14 at 03:08
  • My co-worker commented yesterday, "You really seem to be into Haskell". I think it's time for me to pony up and just start learning it than translating mechanisms from it, badly ;) Thank you for the insight and the references, those will really help me. – sea-rob Apr 06 '14 at 03:11
  • Also, this may or may not be valid, but I don't count "interfaces" as subtype polymorphism, because the interfaces don't pass along implementation. My big concern is that sharing *behavior* through inheritance creates all kinds of problems, and chainable parametric polymorphism turns out to be a good alternative. Plus the Haskell guys like it, and I've had great success with my poor translations into my work. – sea-rob Apr 06 '14 at 03:19
  • @RobY sorry, but I feel that your comments contain too many unsound premises for me to helpfully respond. I would suggest 1) reading up on these topics, and 2) doing tons of examples. Make sure to cover algebraic data types, functors, applicatives, and of course monads. Also, a warning: be careful about "blurring" the definition of monad: if you're going to change the definition, is it really worth calling it a monad anymore? If some pattern or data type is useful to you, use it, but if it's not a monad, calling it so will likely confuse other people. –  Apr 06 '14 at 15:56
  • no worries, and thanks! I'm just playing with ideas so far. – sea-rob Apr 06 '14 at 16:33
1

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?

In non-OO languages, yes. In more traditional OO languages, I would say no.

The issue is that most languages don't have type specialization, meaning you can't make Flier<Squirrel> and Flier<Bird> have different implementations. You have to do something like static Flier Flier::Create(Squirrel) (and then overload for each type). Which in turn means you have to modify this type whenever you add a new animal, and likely duplicate quite a bit of code to make it work.

Oh, and In not a few languages (C# for example) public class Flier<T> : T {} is illegal. It won't even build. Most, if not all OO programmers would expect Flier<Bird> to still be a Bird.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • thanks for the comment. I have some more thoughts, but just trivially, even though `Flier` is a parameterized container, no one would consider it to be a `Bird` (!?) `List` is a List not a String. – sea-rob Apr 03 '14 at 15:25
  • @RobY - `Flier` isn't _just_ a container. If you consider it just a container, why would you ever think that it could replace use of inheritance? – Telastyn Apr 03 '14 at 15:33
  • I lost you there... my point is the monad is an enhanced container. `Animal / Bird / Penguin` is usually a bad example, because it brings in all sorts of semantics. A practical example is a REST-ish monad we're using: `Resource.from(uri).get()` `Resource` adds semantics on top of `String` (or some other type), so it's obviously not a `String`. – sea-rob Apr 03 '14 at 15:40
  • @RobY - but then it is in no way related to inheritance either. – Telastyn Apr 03 '14 at 15:42
  • Except that it's a different kind of containment. I can put String into Resource, or I could abstract a ResourceString class and use inheritance. My thought is that putting a class in a chaining container is a better way to abstract behavior than to put it into a class hierarchy with inheritance. So "no way related" in the sense of "replacing / obviating" -- yes. – sea-rob Apr 03 '14 at 15:46
  • @RobY - but `Resource` does not expose `string`'s behavior (otherwise your `Flier` not being a `Bird` makes no sense) so is drastically different from `ResourceString : string`. Certainly the container is better in the case where `Resource` simply contains a `string`. – Telastyn Apr 03 '14 at 15:52