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.