Some preliminary ranting
Generally in Haskell, you should avoid thinking of “overloading an operator”. Type classes are more than syntax for overloading operators; rather think of them as abstract axiomatisations of data types. Now if that sounds scary forget the word axiom and consider an example.
A number type N
is a type whose values a
, b
, c
... fulfill properties (laws) such as
a + 0 = a
a * 1 = a
a + (b + c) = (a + b) + c
abs a * signum a = a
It is properties like these which allow you to truely write num-polymorphic code (i.e. that'll work with any numerical type) and still be certain that you'll get meaningful results in the end for all of them. That really is the power of overloading. Just being able to re-use a nice short identifier such as +
may be handy in itself, but really wouldn't gain you much over custom operators like +.
for specialised num types (that's how O'Caml does it).
Now, for such laws to be formulable, you need to have all these operators available, and that's why we have classes which require us to not just overload whichever operator we fancy right now, but a whole coherent group of them.
Start reading here if you haven't already...
Classes aren't some special built-in magic. You can easily define your own classes†, or, for standard classes like Num
, look up where they're defined. The easiest way to find class definitions (or any other library declarations) is the Hayoo seatch engine. It will for e.g. +
guide you right away to the (+) :: a -> a -> a
Class Method on hackage. There you see how +
is contained in
class Num a where
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
(+), (-), (*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
So if you want to overload the +
operator you'll need to define all‡ of (+)
, (-)
, (*)
, negate
, abs
, signum
and fromInteger
.
Whoa, but maybe you didn't even want something like abs
!
Very reasonable. But then your type is evidently not really a number type! IMO you shouldn't make anything a Num
instance that's not just a representation of a single number. Note that there are simpler classes available: you probably want AdditiveGroup
class AdditiveGroup v where
zeroV :: v
(^+^) :: v -> v -> v
negateV :: v -> v
(^-^) :: v -> v -> v
This is a class for more general vector spaces. You can easily make your type an instance:
instance AdditiveGroup Pair where
zeroV = Pair (0,0)
Pair (a,b) ^+^ Pair (c,d) = Pair (a+c, b+d)
Pair (a,b) ^-^ Pair (c,d) = Pair (a-c, b-d)
negateV (Pair (a,b)) = Pair (-a ,-b)
You notice that this class doesn't have a multiplication. Well, it turns out that mathematically, multiplication doesn't really make all that much sense on tuples. There are other kind of multiplications though (scalar multiplications) that make sense on vector spaces. Browse the documentation of the vector-space package.
On a different note
Also consider whether you even need AdditiveGroup instance. Daniel Wagner uses in his answer a class that's way more general than numbers, and can be used for all kinds of container-types. Argumably, your Pair
is more of a container for numbers, than a number per se. Therefore, I recommend only implementing
data Pair a = Pair a a
instance Functor Pair where
fmap f (Pair x y) = Pair (f x) (f y)
instance Applicative Pair where
pure v = Pair v v
Pair f g <*> Pair x y = Pair (f x) (g y)
Functor
is really simple: it allows you to apply a function to all elements in a container. Applicative
basically allows you to cross-apply functions between two containers.
With these instances you can, instead of using numerical operators directly, add pairs by writing liftA2 (+) (Pair 1 2) (Pair 3 4)
whenever you need that operation. A bit more verbose, but also a good deal more explicit!
†Frankly, it's better not to do that too often though. Classes as I said are meant to represent deep mathematical axiomatisations; this is not possible for anything that OO programmers would write a class for. Nor is it necessary to write custom classes all the time: data
alone can get you pretty far in a functional language.
‡Actually it's sufficient to implement either (-)
or negate
. That's what the MINIMAL
pragma tells you.