Learn You a Haskell for Great Good! - Miran Lipovaca [15]
The Enum Type Class
Enum instances are sequentially ordered types—their values can be enumerated. The main advantage of the Enum type class is that we can use its values in list ranges. They also have defined successors and predecessors, which we can get with the succ and pred functions. Some examples of types in this class are (), Bool, Char, Ordering, Int, Integer, Float, and Double.
ghci> ['a'..'e']
"abcde"
ghci> [LT .. GT]
[LT,EQ,GT]
ghci> [3 .. 5]
[3,4,5]
ghci> succ 'B'
'C'
The Bounded Type Class
Instances of the Bounded type class have an upper bound and a lower bound, which can be checked by using the minBound and maxBound functions:
ghci> minBound :: Int
-2147483648
ghci> maxBound :: Char
'\1114111'
ghci> maxBound :: Bool
True
ghci> minBound :: Bool
False
The minBound and maxBound functions are interesting because they have a type of (Bounded a) => a. In a sense, they are polymorphic constants.
Note that tuples whose components are all instances of Bounded are also considered to be instances of Bounded themselves:
ghci> maxBound :: (Bool, Int, Char)
(True,2147483647,'\1114111')
The Num Type Class
Num is a numeric type class. Its instances can act like numbers. Let’s examine the type of a number:
ghci> :t 20
20 :: (Num t) => t
It appears that whole numbers are also polymorphic constants. They can act like any type that’s an instance of the Num type class (Int, Integer, Float, or Double):
ghci> 20 :: Int
20
ghci> 20 :: Integer
20
ghci> 20 :: Float
20.0
ghci> 20 :: Double
20.0
For example, we can examine the type of the * operator:
ghci> :t (*)
(*) :: (Num a) => a -> a -> a
This shows that * accepts two numbers and returns a number of the same type. Because of this type constraint, (5 :: Int) * (6 :: Integer) will result in a type error, while 5 * (6 :: Integer) will work just fine. 5 can act like either an Integer or an Int, but not both at the same time.
To be an instance of Num, a type must already be in Show and Eq.
The Floating Type Class
The Floating type class includes the Float and Double types, which are used to store floating-point numbers.
Functions that take and return values that are instances of the Floating type class need their results to be represented with floating-point numbers in order to do meaningful computations. Some examples are sin, cos, and sqrt.
The Integral Type Class
Integral is another numeric type class. While Num includes all numbers, including real number integers, the Integral class includes only integral (whole) numbers. This type class includes the Int and Integer types.
One particularly useful function for dealing with numbers is fromIntegral. It has the following type declaration:
fromIntegral :: (Num b, Integral a) => a -> b
Note
Notice that fromIntegral has several class constraints in its type signature. That’s completely valid—multiple class constraints are separated by commas inside the parentheses.
From its type signature, we can see that fromIntegral takes an integral number and turns it into a more general number. This is very useful when you want integral and floating-point types to work together nicely. For instance, the length function has this type declaration:
length :: [a] -> Int
This means that if we try to get the length of a list and add it to 3.2, we’ll get an error (because we tried to add an Int to a floating-point number). To get around this, we can use fromIntegral, like this:
ghci> fromIntegral (length [1,2,3,4]) + 3.2
7.2
Some Final Notes on Type Classes
Because a type class defines an abstract interface, one type can be an instance of many type classes, and one type class can have many types as instances. For example, the Char type is an instance of many type classes, two of them being Eq and Ord, because we can check if two characters are equal as well as compare them in alphabetical order.
Sometimes a type must first be an instance of one type class to be allowed to become an instance of another. For example, to be an instance of Ord,