While digging through Haskell’s base libaries the other day, I came across these surprising instances for Data.Complex, which seems intended to be a nice way to work with complex numbers:

instance Applicative Complex
instance Monad Complex

It’s strange to see monads and applicatives for this kind of thing in the first place (is a tuple Num n => (n, n) a monad?). However, i found it especially interesting how these specific instances also lead to some pretty weird numeric behavior, if you’re expecting Complex to act anything like complex numbers in math.

First of all, pure puts its argument in both the real and imaginary part. So $$\mathrm{pure}\:1$$ becomes $$1 + i$$

$pure 1 :: Complex Double 1.0 :+ 1.0 This is because pure just pairs its argument with itself: pure a = a :+ a. Because Complex was made so general, it doesn’t have access to a notion of “zero”, which you’d need if you wanted to make this act more like pure a = a :+ 0. The bind operation has more going on, so has more opportunity to seem bizarre from a math standpoint. If you try to add a “plain” number to a complex, you get oddities like this: $ 1 :+ 2 >>= (+1)
2.0 :+ 0.0

And binding to a function of a complex does this piecewise thing (whereas $$(2+3i)(4+5i)$$ in math would be $$-7+22i$$).

\$ 2 :+ 3 >>= (*(4 :+ 5))
8.0 :+ 15.0

Here’s the implementation that makes this behavior happen. It’s applying the bound function to both sides, but then a possible “combining step” is cut in favor of just taking the real part of the real part, and the imaginary part of the imaginary part.

instance Monad Complex where
a :+ b >>= f = realPart (f a) :+ imagPart (f b)

This seems like a case of being a little too parametric for your own good. (It’s also a case of seeing a monad where a monad may not really be the best model.) So, these instances on Complex act more like they’re for a general “pairing” than they are for complex numbers.