I've been playing around with basic functions in Haskell, and am a little confused with the difference between the following type declarations for the function f
f :: Integer -> Integer
versus
f :: Integral n => n -> n
So far, I've treated both of these as identical, but I'm sure this isn't true. What is the difference?
Edit: As a response to the first answer, I wanna propose a similar example which more is along the lines of the question I hold.
Consider the following declarations
f :: Num n => n -> n
or
f :: Num -> Num
What functionality does each offer?
Let's rename:
f :: Integer -> Integer
g :: (Integral n) => n -> n
I like to follow a fairly common practice of adding parentheses to the constraint section of the signature. It helps it stand out as different.
f :: Integer -> Integer
is simple, it takes an integer and returns another integer.
As for g :: (Integral n) => n -> n
: Integral
is not a type itself, rather it's more like a predicate. Some types are Integral
, others aren't. For example, Int
is an Integral
type, Double
is not.
Here n
is a type variable, and it can refer to any type. (Integral n)
is a constraint on the type variable, which restricts what types it can refer to. So you could read it like this:
g
takes a value of any typen
and returns a value of that same type, provided that it is anIntegral
type.
If we examine the Integral
typeclass:
ghci> :info Integral
class (Real a, Enum a) => Integral a where
quot :: a -> a -> a
rem :: a -> a -> a
div :: a -> a -> a
mod :: a -> a -> a
quotRem :: a -> a -> (a, a)
divMod :: a -> a -> (a, a)
toInteger :: a -> Integer
{-# MINIMAL quotRem, toInteger #-}
-- Defined in ‘GHC.Real’
instance Integral Word -- Defined in ‘GHC.Real’
instance Integral Integer -- Defined in ‘GHC.Real’
instance Integral Int -- Defined in ‘GHC.Real’
We can see 3 builtin types which are Integral
. Which means that g
simultaneously has three different types, depending on how it is used.
g :: Word -> Word
g :: Integer -> Integer
g :: Int -> Int
(And if you define another Integral
type in the future, g
will automatically work with that as well)
The Word -> Word
variant is a good example, since Word
s cannot be negative. g
, when given a positive machine-sized number, returns another positive machine-sized number, whereas f
could return any integer, including negative ones or gigantic ones.
Integral
is a rather specific class. It's easier to see with Num
, which has fewer methods and thus can represent more types:
h :: (Num a) => a -> a
This is also a generalization of f
, that is, you could use h
where something with f
's type is expected. But h
can also take a complex number, and it would then return a complex number.
The key with signatures like g
's and h
's is that they work on multiple types, as long as the return type is the same as the input type.
According to this link the standard instances of the Integral
type class are Integer
and Int
. Integer
is a primitive type, and it acts like an unbounded mathematical integer. So given:
f :: Integral n => n -> n
n
could be Int
or Integer
or any "custom" type that you define that is an instance
of Integral
. The use of type classes allows for type polymorphism.
f :: Num -> Num
WILL NOT COMPILE because Num
is not a type. Num
has kind * -> Constraint
and is thus a type constructor whereas f
requires an ordinary type or monotype which has kind *
, such as Int
or Integer
(also known as type system primitives). See the haskell wiki for a brief reference/jumping off point about kinds. Haskell has a rich type system with higher-order types, if used correctly it can be an extremely powerful tool i.e. type polymorphism.
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