Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in Function typing in Haskell

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?

like image 304
Rushabh Mehta Avatar asked Oct 08 '18 15:10

Rushabh Mehta


Video Answer


2 Answers

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.

like image 105
luqui Avatar answered Sep 20 '22 23:09

luqui


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.

like image 30
dopamane Avatar answered Sep 19 '22 23:09

dopamane