Difference in Function typing in Haskell

  • A+

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 


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 


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 type n and returns a value of that same type, provided that it is an Integral 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 Words 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.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: