Reputation: 3025
Say I want to write a function to decide whether a given integer number is prime, which type signature should I use?
isPrime :: Int -> Bool
or
isPrime :: (Integral a) => a -> Bool
What's the difference? Is there a particular reason to choose one over the other?
If so, in which situations should I use the two respectively?
Upvotes: 10
Views: 1617
Reputation: 183888
I would recommend the signature
isPrime :: Integer -> Bool
A signature isPrime :: Int -> Bool
would exclude fast tests for largish numbers, since those tests would often overflow then (technically that is also true of Integer
, at least in the version provided by integer-gmp
, but you will most likely be out of memory long before that matters, so we can maintain the fiction of an infinite Integer
type).
A type isPrime :: Integral a => a -> Bool
would be a lie with any feasible implementation. One could have an instance of Integral
for a type modeling Z[sqrt(2)]
(though for such a type toInteger
would be unfaithful), for such a type, 2 would not be a prime, how would one detect that with a generic test?
Or consider finite types modeling a factor ring Z/(n)
. An Ord
instance for such types would be incompatible with the arithmetic, but we already have that for Int
etc. For example, in Z(6) = {0,1,2,3,4,5}
, the primes would be 2, 3 and 4 (note that none of them is irreducible, 2 = 4*2, 3 = 3*3, 4 = 2*2).
So the only meaningful and feasible test is, "is it a rational (or natural) prime whose value happens to be in the range of the type?". That is captured (as good as possible without sacrificing too much speed) in the type isPrime :: Integer -> Bool
, to be combined with a toInteger
when appropriate.
Upvotes: 4
Reputation: 11
Many primality tests are probabilistic and need random numbers, so at least at the lowest level they would have a type signature like this:
seemsPrime :: (Integral a) => a -> [a] -> Bool
An Integral constraint seems reasonable, because usually you do not need a concrete type, but only operations like rem
.
Upvotes: 1
Reputation: 77384
The type Int -> Bool
means that your function operates on values of type Int
, which are size-limited integers (the maximum size being, I believe, machine-dependent).
The type (Integral a) => a -> Bool
means that your function operates on values of any type that has an instance of the Integral
type class--i.e., types that behave like integers in a particular way. The main reason to chose this over a concrete type is to create a more general-purpose function.
Generic forms using Integral
tend to be most useful when you need to work with integer-like types in other contexts--a good example being places where the standard library fails to do so, e.g. functions like replicate :: Int -> a -> [a]
. Code that operates on some specific integer-like type for its own purposes that wants to use that type with replicate
therefore needs to convert to Int
first, or import genericReplicate
from Data.List
.
What you might want to consider in your case is instead the type Integer
, which represents integers of arbitrary size. Since your main goal is the calculation, there's less value to supporting arbitrary integral types.
If memory serves me, the only instances of Integral
in the standard library are Int
and Integer
anyhow. (EDIT: As hammar reminds me in the comments, there are also instances for fixed-size types in Data.Int
and Data.Word
. There are also foreign types like CInt
but I was disregarding those intentionally.)
Upvotes: 11