Organizing types in Haskell

Monday, Dec 7, 2020
Functional Haskell

Organizing values into various types based on what can and cannot be done with a value allows us to write type safe programs. So now we cannot divide v1 :: Int by v2 :: Char. This is good. But soon we realize that some functions can work with more than one type. For example, returning the first element of a list or swapping values in a pair. So now we have to write separate functions for each type. This is not good. Ideally, we want a single function to work with different types in such cases. Let's see how Haskell solves this problem.

Polymorphic types

If a function makes sense for any type, we can use type variable instead of concrete type. How? By simply replacing the concrete type with a lower case letter. For example,

-- instead of writing head function's signature like:
head :: [Int] -> Int

-- we can write
head :: [a] -> a

The second signature reads like, “for any type a, head maps a list of such type to a single value of such type”. Good. Now we can write our head and swap functions like:

head :: [a] -> a
head [] = error "empty list"
head (x:xs) = x

swap :: (a,b) -> (b,a)
swap (x,y) = (y,x)

Any type using one or more type variable is called polymorphic and functions that use such types are called polymorphic functions.

Overloaded types

If a function makes sense for more than one type but not every type out there, we can use class constraint. For example, the multiplication operator (*) can be applied to numbers of any numeric type (Int, Integer, Float, Double) but not to Char, Bool or any other type. The operations that support multiple types are called overloaded operators. We write class constriant as C a, where ‘C’ is the class and ‘a’ is the type variable. Any type using one or more class constraints are called overloaded types. So we write the type of * as follows:

(*) :: Num a => a -> a -> a

This signature is read as, “for any type a that is an instance of class Num, * has type a -> a -> a”

Type classes

The notion of class constraint allow us to create Type classes - collection of types supporting one or more overloaded operations. Following are some common built-in type classes:

Equality types (Eq)

Collection of types that can be compared for equality and inequality. All basic types as well as tuple and lists containing those basic types are instances of Eq class.

Ordered types (Ord)

Types that are instances of Eq class but also whose values can be compared using >, >=, <, <=, min and max operations. Again, all basic types and lists/tuples containing those types are instances of Ord class.

Showable types (Show)

Types whose values can be converted into string using show :: a -> String method.

Readable types (Read)

Types whose values can be converted from string using read :: String -> a method. Once again, all basic types alongwith lists/tuples containing those types are instances of Show and Read class.

Numeric types (Num)

Types whose values are numeric and thus support -, +, *, negate, abs and signum methods. Int, Integer, Float and Double are instances of Num class.

Integral types (Integral)

Types whose values are instances of Num class but are also integers and support div and mod methods.

Fractional types (Fractional)

Types whose values are instances of Num class but are also non-integers and thus support / and recip methods.