From ghci:
Prelude> :i Num
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
-- Defined in `GHC.Num'
instance Num Word -- Defined in `GHC.Num'
instance Num Integer -- Defined in `GHC.Num'
instance Num Int -- Defined in `GHC.Num'
instance Num Float -- Defined in `GHC.Float'
instance Num Double -- Defined in `GHC.Float'
Why is (+)
, (-)
part of the Num class to begin with?
For example - you could easily define this type class:
class Plus a where
(+) :: a -> a -> a
And then let:
instance Plus [] where
(+) = (++)
And you could also define these for sets to mean set union, or add (-)
to a type class to mean set difference... And it makes no sense to define signum
on a list.
Of course I could create my own type class that uses (|+|)
instead
- but why was these operators reserved in haskell for Num alone?
So why was this choice made? Is it due to legacy or has no one wanted this?
My guess is that they intended Num
to reflect the mathematical nature of numbers, rather than some more general structure. The operation in Num
are largely those that support an abstract mathematical structure called a ring, of which the integers (and any extensions thereof) are a prominent example.
Since Haskell allows you to define your own operators, you can set up your own notation. Then, when others read your code, they will be able to tell that you are working with something which is not Num
ber-like, but perhaps more general.
There are many unfortunate historical design decisions in the Haskell prelude, which cannot be fixed because they would break compatibility. However, IMHO, the basic design of the Num
typeclass and operators is sound.
A lot of it is due to historic reasons, but there are also mathematical ones. For example, there are already mathematical names for structures that support binary operators. The most commonly used one is a Monoid, which you can use in Data.Monoid
. This typeclass defines a function mappend
and a value mempty
, the equivalent of the identity element for mappend
, and there is an operator alias for mappend
called <>
. Lists and many other objects form monoids, and numbers actually form 2, with +
and with *
, where the identity elements are 0 and 1 respectively. Structures that have an identity, an associative binary operation, and an inverse for that operation (subtraction is the inverse of addition, for example) are called groups (not part of the standard library), and structures that form a group under one operator and a monoid under a second operator are called rings. These objects are the basis for algebraic structures/abstract algebra classes.
These mathematical constructs are somewhat tricky to implement in Haskell, at least very well. There are overlapping instances for Monoid
for all Num
types with +
and *
, and for some numeric types there's a Group
overlap if /
can be defined such that dividing by 0 is well defined (some structures can allow this). These overlapping instances lead to lots of newtypes that make it difficult to work on a day to day basis. The Num
typeclass helps here because it provides a useful interface, that of manipulating and performing operations on numbers, that is easy to use in real world code rather than just in academia. There have been attempts to introduce a more mathematical version of Prelude
, and some people use these with varying success, but your average Haskeller would rather ditch mathematical purity for a more practical interface.
In short, the Num
typeclass is defined this way for historical reasons, but also for very practical reasons. A more strict mathematical construction is unwieldy, and for many types simply using Data.Monoid
's <>
operator is perfect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With