37

Do the compiler or the more "native" parts of the libraries (IO or functions that have access to black magic and the implementation) make assumptions about these laws? Will breaking them cause the impossible to happen?

Or do they just express a programming pattern -- ie, the only person you'll annoy by breaking them are people who use your code and didn't expect you to be so careless?

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
Owen
  • 38,836
  • 14
  • 95
  • 125

3 Answers3

60

The monad laws are simply additional rules that instances are expected to follow, beyond what can be expressed in the type system. Insofar as Monad expresses a programming pattern, the laws are part of that pattern. Such laws apply to other type classes as well: Monoid has very similar rules to Monad, and it's generally expected that instances of Eq will follow the rules expected for an equality relation, among other examples.

Because these laws are in some sense "part of" the type class, it should be reasonable for other code to expect they will hold, and act accordingly. Misbehaving instances may thus violate assumptions made by client code's logic, resulting in bugs, the blame for which is properly placed at the instance, not the code using it.

In short, "breaking the monad laws" should generally be read as "writing buggy code".


I'll illustrate this point with an example involving another type class, modified from one given by Daniel Fischer on the haskell-cafe mailing list. It is (hopefully) well known that the standard libraries include some misbehaving instances, namely Eq and Ord for floating point types. The misbehavior occurs, as you might guess, when NaN is involved. Consider the following data structure:

> let x = fromList  [0, -1, 0/0, -5, -6, -3] :: Set Float

Where 0/0 produces a NaN, which violates the assumptions about Ord instances made by Data.Set.Set. Does this Set contain 0?

> member 0 x
True

Yes, of course it does, it's right there in plain sight! Now, we insert a value into the Set:

> let x' = insert (0/0) x

This Set still contains 0, right? We didn't remove anything, after all.

> member 0 x'
False

...oh. Oh, dear.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 3
    Sorry I can only accept one answer -- that's a really cool example. Thank you for that. – Owen Jun 19 '11 at 05:30
  • 1
    Great example! This also shows that Haskell should have had a more complete range of type classes. If there would have been a partial ordering class, Float would have had an instance of that instead of being crammed into Ord. Then the Set implementers probably would have had the idea of using a list of binary trees, so that their implementation would have worked on partial orderings. – Sjoerd Visscher Jun 20 '11 at 16:14
  • 3
    @Sjoerd Visscher: Actually, I argue that what we really want is a "meaningless ordering" type class. In many cases we don't *care* what ordering `Set` uses, so long as it is a consistent total ordering. Arbitrarily giving NaN a semantically-meaningless place in the ordering would make `Set` work as-is, while a partial order type class would provide the correct semantic ordering on floats. The current situation provides neither a correct arbitrary ordering nor a correct implementation of comparisons according to the IEEE spec. – C. A. McCann Jun 20 '11 at 16:29
  • Note that `(Integer, Integer)`, interpreted as (x, y) coordinates, also have no meaningful `Ord` instance, but do allow an arbitrary total order (i.e., the `Ord` instance it currently gets). – C. A. McCann Jun 20 '11 at 16:32
  • 1
    To be fair, you did try to insert a value `x` such that `x /= x`. If we interpret the `Eq` class as giving a PER instead of an equivalence relation, then `0/0` is a value that isn't part of the "semantic" domain of the type and all bets are off if you use it. At least that is how I try to rationalize this behaviour. – Russell O'Connor Jun 21 '11 at 10:11
  • 1
    @Russell O'Connor: `Eq` is easier to rationalize, even though it's clearly *intended* to be an equivalence relation; also, linear search by equality through the example list works correctly. The bigger problem is `Ord`, which is even specified in the Haskell Report to be a total order, and is what causes the buggy behavior in `Data.Set.Set`. Note that `compare (0/0) (0/0)` = `GT`. My preference, as above, would be to have partial, total, and non-semantic-total versions of both `Eq` and `Ord`. – C. A. McCann Jun 21 '11 at 13:44
26

The compiler doesn't make any assumptions about the laws, however, if your instance does not obey the laws, it will not behave like a monad -- it will do strange things and otherwise appear to your users to not work correctly (e.g. dropping values, or evaluating things in the wrong order).

Also, refactorings your users might make assuming the monad laws hold will obviously not be sound.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 1
    Another consideration is that many of the combinators provided for working with monads are only unique if you assume some subset of the monad laws. – Edward Kmett Jun 20 '11 at 20:46
  • @Edward -- what do you mean by unique? Doesn't every function application give a unique result? – Owen Jun 21 '11 at 03:38
  • 2
    Owen, yes but in some sense many functions have a canonical meaning given just the laws and the types in play -- irrespective of the choice of implementation. liftM and fmap *have* to do the same thing by the monad laws without even looking at their bodies. Lose those and it isn't guaranteed to be a valid default definition. – Edward Kmett Jun 21 '11 at 14:29
  • @Edward: You say "liftM and fmap _have_ to do the same thing" because of their types -- but that's not quite true. Compare liftMWrong f ma = ma >> ma >> ma >> ma >>= \a -> return (f a). So what do you really mean? – Daniel Wagner Jun 23 '11 at 03:27
  • 2
    Daniel: Given the types _and_ the laws. You can't comply with both the monad and functor laws with instances that do different things. liftMWrong means that either the 'fmap id = id law' is broken if you use it as fmap or the 'fmap f xs == xs >>= return . f' is broken if you do not. – Edward Kmett Jun 23 '11 at 06:29
  • 2
    It's not particularly hard to teach GHC about some of the monad laws though. {-# RULES "return/bind" forall f x . return x >>= f = f x #-} for example, is potentially useful. Perhaps it should be in the standard library. – svenningsson Jun 23 '11 at 08:31
12

For people working in more "mainstream" languages, this would be like implementing an interface, but doing so incorrectly. For example, imagine you're using a framework that offers an IShape interface, and you implement it. However, your implementation of the draw() method doesn't draw at all, but instead merely instantiates 1000 more instances of your class.

The framework would try to use your IShape and do reasonable things with it, and God knows what would happen. It'd be kind of an interesting train wreck to watch.

If you say you're a Monad, you're "declaring" that you adhere to its contract and laws. Other code will believe your declaration and act accordingly. Since you lied, things will go wrong in unforeseen ways.

Charlie Flowers
  • 17,338
  • 10
  • 71
  • 88