You could make functions named +
and -
that work on seconds, but there's no way for it to be the same +
and -
from the Num
type class without making Seconds
an instance of Num
(which therefore will lead any code that gets a Seconds
value as a generic Num a
to expect that it can use the other Num
functions as well).
All you have to do is explicitly import the Prelude, either hiding +
and -
or importing it qualified.
The trouble then is that any code using your +
and -
also has to do something to resolve the ambiguity with the Prelude +
and -
; either you only have one version of +
in scope, or at least one of them must always be referred to with a qualified name (some variant of Prelude.+
, P.+
, S.+
, Seconds.+
, etc). For an obscure name, this is sometimes acceptable. It's probably not a good idea with something as common and fundamental as +
.
You could make that option nicer by making +
and -
functions in a new type class (say PlusMinus
), and write instance Num a => PlusMinus a where (+) = (Prelude.+)
etc. You then also make Seconds
an instance of PlusMinus
.1
What this buys you is that any code that wants to use your new +
operator can at least safely hide the Prelude's +
while still being able to use +
on other Num
types. It does still impose some bother on every module wanting to use your +
though, and it has the potential to be confusing (someone one day may see +
being used on Seconds
without being deeply familiar with all this, and assume that they can use other numeric operations on Seconds
).
Probably better would be to make functions that aren't called +
and -
. You can use new multi-character operators containing +
and -
if you want (though it can be tricky to find ones that aren't used by other libraries).
Here's an approach I once took that was sort-of massive overkill, but also sort-of satisfying.
The problem was that I had vectors representing absolute positions, and also vectors representing offsets. I decided it made sense to add and subtract offsets, but not positions. However it did make sense to add an offset to a position to get a position, or to subtract two positions to get an offset, and even to multiply an offset by a scalar to get an offset.
So what I ended up doing was to define a type class something like this:
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-}
class Addable a b where
type Result a
(|+|) :: a -> b -> Result a b
instance Addable Offset Offset where
type Result Offset Offset = Offset
o |+| o = ...
instance Addable Position Offset where
type Result Position Offset = Position
p |+| o = ...
instance Addable Offset Position where
type Result Offset Position = Position
o |+| p = p |+| o
etc
So you end up using |+|
rather than +
, but it still ends up looking a bit like the algebra you're used to thinking in (once you get used to the convention that |+|
is the "generalised" version of +
, etc), and it lets you encode a lot of rules about what operations make sense in the type system, so the compiler can check them for you. The downside is a lot of boilerplate defining all the instances, but for a small fixed number of types that's something you only have to do once.
1You'll need extensions to make this work; it's a little unsafe in principle because there could be an instance of Num
for Seconds
out there somewhere, which would make Seconds
match PlusMinus
two different ways.