It seems like the common pattern of taking/returning Int
(ie ByteString.hGet
and Data.List.length
) is contrary to the Haskell pattern of using strongly-descrbing types, since many of these cases can only handle positive numbers. Would it not be better to use Word
, or is there a reason these functions are partial on Int
?

- 10,999
- 5
- 47
- 71
-
3In part historical accident, in part because the underlying low-level structures and functions take `Int` arguments (again by historical accident, at least partially). For example, all the low-level indexing operations of GHC's array implementation use `Int#` indices. – Daniel Fischer Sep 14 '12 at 22:10
-
1@DanielFischer that looks like an answer. Could you add it as one? :) – singpolyma Sep 14 '12 at 22:15
-
8Better yet, why in the world is there no `Natural` type corresponding to `Integer`? >:[ This is a perpetual source of annoyance to me. – C. A. McCann Sep 14 '12 at 22:21
-
And a perpetual question (or, it used to be in the days I paid attention to -cafe). – Thomas M. DuBuisson Sep 14 '12 at 22:25
-
1@C.A.McCann: It's particularly annoying because you can't really define a good one yourself. `fromInteger` would either have to behave oddly--returning the absolute value of its argument, or something--or it would have to be partial. There's no obvious way to write a numeric type that would give you a type error for negative literals. (Maybe with rebindable syntax to change the meaning of negate? I don't know...) – Tikhon Jelvis Sep 15 '12 at 01:54
-
1@TikhonJelvis: Haha. As far as literals go, `fromInteger` is only applied to positive values, which really just irritates me further. If `negate` and `fromInteger` are in separate type classes, literals work just fine. Problem is your `fromNatural` is still misnamed and you can apply it to negative non-literals... – C. A. McCann Sep 15 '12 at 02:08
3 Answers
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.
-
5According to §23.1 in the Haskell 2010 report, "a `Word` is an unsigned integral type, with the same size as `Int`." – Stefan Sep 22 '15 at 05:17
The same reasoning applies as in C. The reason to use more precise types is to prevent mistakes. Mistakes, in this case, such as trying to use negative numbers where they are not meaningful. But the behaviour of Word
on over- or underflow, as of unsigned int
in C, is to wrap around. If you attempt to use a negative number where a Word
(or unsigned int
) is expected, you don't get the compiler yelling at you, or even an exception at runtime. You get a large positive number. Which you have no way of distinguishing from any other ("legitimate") large positive number!
Look at this:
Prelude Data.Word> -1 :: Word
4294967295
Instead of making mistakes impossible to commit, you have made them impossible to detect. With Int
(and int
), you at least have the possibility of checking for negative values manually. With Word
and unsigned int
, you have nothing.
What would be valuable is an unsigned type that reacts to over- or underflow by throwing an exception. That still wouldn't make mistakes impossible, but it would make them easier to detect. However, it would come at a performance cost.* I don't know if it's possible to rule them out at compile time, but it doesn't seem easy.
* At least, x86 requires an extra instruction -- after every operation! -- to check whether over- or underflow occured. I don't know if there's an architecture that does it "for free", though it would be nice. Or maybe a distinguished NaN value like we have for floating point numbers (perhaps instead of the most negative number) which would be used to denote unrepresentable values...

- 7,695
- 3
- 30
- 41
-
10What? Haskell's `Int` has exactly the same problem `(3::Int)^2^2^2^2 = -5843219465185787903` – Jeff Burdges Dec 24 '14 at 17:51
-
1If you don't have over/underflow checking then all of the numeric types are vulnerable to over/underflow, yeah. But it's a *lot* easier to accidentally underflow an unsigned int (just by subtracting small numbers from each other) than to over/underflow a signed one, so from a practical perspective it makes a difference. (And yeah you /could/ also check whether an unsigned int's value is in the upper half of its range, the only argument against that is that it's unusual.) – glaebhoerl Jun 29 '20 at 14:53
My first guess is that unsigned arithmetic has some issues that would cause stupid bugs if you'r not paying attention:
Prelude Data.Word> let x = 0 :: Word in if x - 1 > x then "Ouch" else "Ohayoo"
"Ouch"
It will have some issues in polymorphic functions that appears to be correct:
Prelude Data.Word> let f acc x y = if x <= y then f (acc + 1) x (y - 1) else acc
Prelude Data.Word> :t f
f :: (Num a1, Num a, Ord a) => a1 -> a -> a -> a1
Using a standard length function:
Prelude Data.Word> let len' x = if null x then 0 else 1 + len' (tail x) :: Int
Prelude Data.Word> :t len'
len' :: [a] -> Int
Prelude Data.Word> f 0 0 (len' [1,2,3])
4
And using a length function returing Word
:
Prelude Data.Word> let len x = if null x then 0 else 1 + len (tail x) :: Word
Prelude Data.Word> :t len
len :: [a] -> Word
Prelude Data.Word> f 0 0 (len [1,2,3])
...diverges...
And, of course, if those functions returned Word
instead of Int
, you introduce the need to keep transforming your Word
s into Int
s to use it in other common places.

- 5,221
- 2
- 28
- 39