Difference in Function typing in Haskell

  • A+
Category:Languages

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 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.

Comment

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