It is true that the expressiveness of the Haskell type system encourages users to assign precise types to entities they define. However, seasoned Haskellers will readily acknowledge that one must strike a balance between ultimate type precision (which besides isn't always attainable given the current limits of Haskell type system) and convenience. In short, precise types are only useful to a point. Beyond that point, they often just cause extra bureaucracy for little to no gain.
Let's illustrate the problem with an example. Consider the factorial function. For all n
bigger than 1, the factorial of n
is an even number, and the factorial of 1 isn't terribly interesting so let's ignore that one. Therefore, in order to make sure that our implementation of the factorial function in Haskell is correct, we might be tempted to introduce a new numeric type, that can only represent unsigned even integers:
module (Even) where
newtype Even = Even Integer
instance Num Even where
...
fromInteger x | x `mod` 2 == 0 = Even x
| otherwise = error "Not an even number."
instance Integral Even where
...
toInteger (Even x) = x
We seal this datatype inside a module that doesn't export the constructor, to make it abstract, and make it an instance of the all the relevant type classes that Int
is an instance of. Now we can give the following signature to factorial:
factorial :: Int -> Even
The type of factorial
sure is more precise than if we just said that it returns Int
. But you'll find that definining factorial
with such a type is really quite annoying, because you need a version of multiplication that multiplies an (even or odd) Int
with an Even
and produces and Even
. What's more, you might have to introduce extraneous calls to toInteger
on the result of a call to factorial
in client code, which can be a significant source of clutter and noise for little gain. Besides, all these conversion functions could potentially have a negative impact on performance.
Another problem is that when introducing a new, more precise type, you often end up having to duplicate all manner of library functions. For instance, if you introduce the type List1 a
of non-empty lists, then you will have to reimplement many of the functions that Data.List
already provides, but for [a]
only. Sure, one can then make these functions methods of ListLike
type class. But you quickly end up with all manner of adhoc type classes and other boilerplate, with again not much gain.
One final point is that one shouldn't consider Word
to be an unsigned variant of Int
. The Haskell report leaves the actual size of Int
unspecified, and only guarantees that this type should be capable of representing integers in the range [? 229, 229 ? 1]. The type Word
is said to provide unsigned integers of unspecified width. It isn't guaranteed that in any conforming implementation the width of a Word
corresponds to the width of an Int
.
Though I make a point guarding against excessive type proliferation, I do acknowledge that introducing a type of Natural
of naturals could be nice. Ultimately, though, whether Haskell should have a dedicated type for natural numbers, in addition to Int
, Integer
, and the various Word*
types, is largely a matter of taste. And the current state of affairs is probably in very large part just an accident of history.