Andrew Gibiansky   ::   Math → [Code]

Typeclasses: Polymorphism in Haskell

Saturday, July 12, 2014

This is the third of several blog posts meant to serve as a crash course in Haskell for someone already familiar with programming and somewhat familiar with functional programming. The previous post in the series was here, and the next post can be found here.

In this guide, we’ll learn about how another way Haskell handles polymorphism. We’ll look at a few of the uses cases for this sort of polymorphism.

Advanced Polymorphism

Let’s begin by considering the polymorphism we’ve already seen and seeing what polymorphic type signatures for functions tell us about the functions.

Polymorphism via Type Variables

Suppose you’d like to create a reverse function which reverses the order of a list:

reverse [1, 2, 3] == [3, 2, 1]
reverse "abc" == "cba"

This function should compltely ignore the elements of the list. It should not evaluate them or use them in any way. It should work for lists of any type of element. All of these are enforced by the type signature of reverse:

-- | Reverse the elements in a list.
reverse :: [a] -> [a]

Since the element type is just a (a type variable), reverse cannot make any assumptions about it. The same exact code must work for Int, Char, String, Handle (a file handle), or any other type.

These sorts of type signatures help eliminate many bugs and can help you figure out what code you want to write. However, while sometimes we can operate on any type variable a, sometimes we’d like to have some constraints on what a can be. For example, consider a hypothetical function elem, which returns whether or not a some element is in a list. Could elem have the following type signature?

-- | Return whether an element exists in a list.
elem :: a -> [a] -> Bool
elem element list = ...

A brief examination of the type signature should make it clear that, no, a working elem function cannot have that type signature. A function with that type signature cannot check whether one value of type a is equal to another value of type a! Thus, there’s no way that it could check that the element is equal to the values in the list. We could, however, create the following function, which is elem specialized to Int values:

-- | Return whether an element exists in a list.
elemInt :: Int -> [Int] -> Bool
elemInt element list = ...

We could implement a valid elemInt, because we know we can compare Int values.

Exercise 1: Implement elemInt

As an exercise, go ahead and implement elemInt. Make sure the following test cases pass:

elemInt 0 []                 == False
elemInt (error "Nothing") [] == False
elemInt 3 [10, 20, 30]       == False
elemInt 3 [1, 2, 3]          == True

We could also implement elemChar, elemString, elemFloat, and many other versions of elem. In order to implement elem, however, we need to have a way to write a type signature that allows polymorphism over the list element type (via a type variable a) but also requires that we can somehow compare values of type a for equality. Haskell provides typeclasses as a mechanism for constrained polymorphism.

Why We Can’t == On Any Type

In some languages, all types will support the equality operator. For example, in C or Java, two values of the same type may always be compared using ==. This comparison tests for pointer or reference equality, not the fact that the underlying objects are equal. (If you’ve programmed in Java, you will have been taught early on that you must use .equals() for objects if you mean to compare their values, because == will only check for reference equality; this can be a cause of many subtle bugs.)

Haskell does not allow testing for equality by pointer comparison for a variety of reasons. (Among others, pointer comparison prevents some optimizations and breaks expected language semantics in some cases.) Instead of using pointer comparions, == will always compare the values of objects.

We cannot have == work on all types because some objects might not have a meaningful comparison operator. For example, what does it mean to check if two file handles are equal? Are the equal if they have the same filename? If they have the same filename and point to the same location on disk? If they point to the same node/location in a file system but have different names (due to hard links), are they equal?

As another example, consider equality of functions of type Int -> Int — it is impossible to check if two functions are equal without running them on all possible inputs, which would take forever. Thus, the == operator only makes sense and only exists for a subset of all types in Haskell.

Typeclasses

Typeclasses are a mechanism for overloading the meaning of names (values and functions) for different types. This allows us to write code that works on multiple types while using values of those types — for example, we can use the == operator to test many different types for equality. The == function is defined as part of the Eq typeclass, which could be written as follows:

class Eq a where (1)
  (==) :: a -> a -> Bool (2)
1 This line declares the typeclass. class and where are keywords, and between them you put the name of the class (Eq) and a type variable (a) that will represent the Eq-able type for the remainder of a declaration. Eq is the name of a constraint on types: in the remainder of the declaration, we write the type signatures of values and functions that must be implemented for a type for it to be part of the Eq typeclass. The type variable a is used in those type signatures.
2 This type signature is the declaration of a method of the typeclass. (Be very careful — typeclasses and typeclass have very little to do with object-oriented classes and methods, although the terminology may be decieving and there may be some surface similarities.) With this type signature, we state that in order to create an instance of the Eq typeclass for some type we must write a function called == that satisfies the given type signature. For example, in order to create an instance of Eq for Int, we would have to write a function with type signature Int -> Int -> Bool which tests two integers for equality.

Haskell already defines instances of Eq for all commonly used data types, which means you can use == on common types such as Int, String, [Int], Maybe String, and most others defined in the standard library. For the sake of understanding typeclasses, let’s define our own tree data structure:

-- | A binary tree containing Int values.
data IntTree = Leaf Int | Tree Int IntTree IntTree

If we tried to write code that used == on IntTree values, GHC would spit out an error message complaining about the lack of the Eq IntTree instance. To satisfy GHC, we can write an instance of Eq for IntTree:

instance Eq IntTree where (1)
  Leaf x == Leaf x' = x == x' (2)
  Tree x left right == Tree x' left' right' =
    x == x' && left == left' && right == right'
  _ == _ = False (3)
1 This line is the beginning of the instance declaration and generally mirrors the class declaration. In this case, we’re declaring an instance Eq IntTree, so you can replace all occurrences of the a type variable in the original class with IntTree.
2 This is the definition of the == operator. To the left of the =, we have match the arguments to == with two patterns, Leaf x and Leaf x' and return True if and only if x == x'. Note that x and x' are of type Int, which means we can use == on them, because we have the instance Eq Int provided for us by Haskell.
3 In order to make sure that == works for all IntTree values, we provide a fall-through pattern match which will match anything the previous patterns haven’t. Since the previous patterns tested leaves against leaves and branches against branches, we know that this pattern is only matched if the structures of the trees are different (there’s a leaf in one tree where there is a branch in another), so we return False because these trees cannot be equal.
Exercise 2: Eq IntList

Consider the following linked list data structure:

data IntList = Nil | Cons Int IntList

Implement the Eq typeclass for the IntList type. Then, verify that the following code works and typechecks:

value1 :: IntList
value1 = Cons 3 (Cons 10 Nil)

value2 :: IntList
value2 = Nil

main = print (value1 == value1,
              value2 == value2,
              not (value1 == value2))

In both the example above (IntTree) and the exercise (IntList), you must use recursion to implement ==. In addition to recursing in the definition of ==, you must eventually invoke the == for the Int type, to compare the values at the leaves of the tree and nodes of the linked list. In the line Leaf x == Leaf x' = x == x', the usage of == on the right hand side refers to == for Int values; this is not a case of recursion, because we aren’t calling == for IntTree values.

In addition to defining their required methods, typeclasses can define auxiliary methods with default implementations. For example, the Eq typeclass is actually defined as follows:

class Eq a where
  (==) :: a -> a -> Bool

  (/=) :: a -> a -> Bool (1)
  x /= y = not $ x == y
1 The (/=) method is not required by the Eq typeclass. If an implementation of /= is not provided, the default implementation not $ x == y is used. Instances are allowed to provide their own custom implementations of /=; custom implementations are often used to provide more efficient implementations of typeclass methods.

Many of the typeclasses in the standard library have several methods but only require one or two of them for a complete implementation.

Common Typeclasses

Typeclasses are fundamental to the Haskell language, and the standard library ships with several very commonly used typeclasses. In this section, we’ll go over several of the simpler typeclasses; we’ll see how they’re defined, how they’re used, and how to write simple instances for them. We skip the Eq typeclass, as it is reviewed in the previous section.

Ord

Types which implement the Ord typeclass can be compared to each other; their values must have a total order imposed on them (for any values x and y, we can compare the two values and determine which one is greater, if any). In order to be a member of the Ord typeclass, a type must have a compare function which returns an ordering. In some languages (C, Java, Python) the compare function must return an integer which is zero if the two values are equal, a positive integer if the first value is greater than the second, and a negative integer if the first value is smaller than the second. In Haskell, orderings are instead expressed using the Ordering type:

data Ordering = LT | EQ | GT

The Ord typeclass then has a compare function which takes two values and returns an Ordering:

compare :: a -> a -> Ordering

In addition, the Ord typeclass includes a few functions that have default implementations using compare but can be overriden for efficiency, such as <, >, max, and min. The full Ord typeclass declaration is as follows:

class Eq a => Ord a where
  -- Required for implementing Ord.
  compare :: a -> a -> Ordering

  -- Functions with default implementations.
  (>) :: a -> a -> Bool
  x > y = compare x y == GT

  (<) :: a -> a -> Bool
  x > y = compare x y == LT

  (>=) :: a -> a -> Bool
  x >= y = compare x y == GT || compare x y == EQ

  (<=) :: a -> a -> Bool
  x <= y = compare x y == LT || compare x y == EQ

  max :: a -> a -> a
  max x y = if x > y then x else y

  min :: a -> a -> a
  min x y = if x < y then x else y

The Ord typeclass, unlike Eq, has a context. Contexts come before type declarations or typeclass heads and can specify that type variables implement some specific typeclass:

class Eq a => Ord a where ...

In the above declaration, the context is Eq a, and is separated from the typeclass head (which is Ord a) using a "fat arrow", =>. The context specifies that the type variable a must be a member of the Eq typeclass in order to implement the Ord typeclass for that variable. In this case, Eq a is required for Ord a because it is nonsensical to have an ordering unless we have equality, since clearly compare can be used to implement (==).

In general, it is wise to make sure that all instances of Ord follow a few rules. First of all, they should agree with instance of Eq; that is, if x == y, then compare x y should return EQ. Instances of Ord should also define a reasonable total order: if compare x y == LT, then compare y x == GT, and if compare x y == EQ then compare y x == EQ as well.

Show

The Show and Read typeclasses allow types to be converted to and from strings. They are not meant for user input and output, but rather for programmer viewing and debugging. (For example, the Show instance for String outputs newlines as \n and quotes as \", which makes sense for programmers but does not for user output.). The Show typeclass has three methods: show, showsPrec, and showList.

Most of the time, knowing about show is enough; the other two are somewhat specialized methods that you will rarely need to implement. show has the type show :: a -> String; it can convert any type a which implements the Show typeclass into a String. For example, in order to convert an integer to a string, you could write show (1 :: Int); in this context, show would be specialized to show :: Int -> String.

For the sake of demonstration, let’s create our own character-like type that can only hold uppercase As, Bs, Cs, as well as a special character representing non-printable character:

data ABC = A | B | C | Other

If we want to be able to print ABC values, we can create a Show instance for it:

instance Show ABC where
  show A = "A"
  show B = "B"
  show C = "C"
  show Other = "<Not printable>"

We can then write programs that print values of type ABC to standard output. The following program will simply print the letter "A" to the screen:

Show1.hs
a :: ABC
a = A

main :: IO ()
main = putStrLn (show a) (1)
1 Instead of writing putStrLn (show x), we can write print x. print is a function defined as print = putStrLn . show.

For most use cases, show is all you need to know about the Show typeclass; for the sake of completeness, we discuss showsPrec and showList, even though these functions come up rarely in practice.

Advanced Uses of Show

To motivate showsPrec, consider the following code:

main = putStrLn (show Other ++ show Other ++ show Other ++ show Other)

How long does this program take to run? Not very long, because we only have four Strings we’re concatenating. However, in general, concatenating n Strings can take O(n^2) time, since each time we append a string to the end of a list, we must first traverse the entire list. If we were to run this program with a thousand ABC values instead of four, this might take quite a while due to this quadratic growth! This quadratic growth is the first problem that ShowS solves.

The fundamental issue is that Show relies on String values, which take a long time to append. To rectify this, showsPrec uses a different type with the alias ShowS:

type ShowS = String -> String

A ShowS value is a function that, when given a String, prepends another String to it and returns the sum. The type String and ShowS are isomorphic in meaning, which we can show by providing conversion functions between them. We can convert a String into a ShowS by writing a function which prepends the given string to its input:

showString :: String -> ShowS
showString str = \next -> str ++ next

Converting from String to ShowS is fast. Since we don’t actually do any work (we just create a function), we don’t need to iterate over the characters, so it is done in constant time. We can also convert from ShowS to a String by using the ShowS to prepend to an empty string:

fromShowS :: ShowS -> String
fromShowS prepender = prepender ""

Unlike showString, fromShowS is not a constant time operation. In order to prepend a string to "", the ShowS must traverse the entire string it’s appending and then add "" onto the end of it. Thus, the runtime of fromShowS grows linearly with the number of characters in the output.

Let’s compare appending String values and ShowS values. In order to append String values, you use the ++ operator, which traverses over the first string character by character and then adds the second string onto the end. As you append more and more characters to a string, appends take longer and longer, because each append must traverse all previous characters; thus, the running time grows quadratically in the length of the string. In constract, in order to append ShowS values, you just use the . function composition operator. If you have a ShowS which prepends the string "x" and a ShowS which prepends the string "y", you can make a ShowS which prepends "xy" by composing your two ShowS values to first prepend "y" and then prepend "x". Since function composition is done in constant time, combining ShowS values only takes as long as the number of values you are combining.

As long as showsPrec outputs a ShowS instead of a String, we can write code that efficiently concatenates the string representations of many things. Using ShowS yields better performance, but it is not as convenient as show for common uses, which is why show is included in the typeclass.

The second problem that showsPrec solves is one of parenthesizing. For example, if we write show (Just [1, 2, 3]), we expect the result to be Just [1, 2, 3]; however, if we write show (Just (Just [1, 2, 3])), we expect the result to be Just (Just [1, 2, 3]). Consider the following attempt at an implementation:

instance Show a => Show (Maybe a) where (1)
  show Nothing = "Nothing"
  show (Just x) = "Just " ++ show x
1 This example uses instance contexts; see Exercise 1 for more information on this.

If you pay attention to what this example does, though, you will notice that show (Just (Just [1, 2, 3])) does not work! Instead of outputting what we want, it outputs Just Just [1, 2, 3], which is missing a set of parentheses.

Using the type alias ShowS, the type of showsPrec for a type a is written as

showsPrec :: Int -> a -> ShowS

The Int that showsPrec is passed is the operator precedence of the enclosing context, which is a number between zero and eleven. Function application has precedence ten; infix data constructors can have lower precedences. This integer allows the showsPrec implementation to decide whether or not to include the parentheses. The following is a proper implementation of Show Maybe, this time using showsPrec:

instance Show a => Show (Maybe a) where
  showsPrec _ Nothing = showString "Nothing"(1)
  showsPrec precedence (Just x) =
    if precedence > 10 (2)
    then showString "(Just " . showsPrec 11 x . showString ")" (3)
    else showString "Just " . showsPrec 11 x (4)
1 showString is the same convenience function we defined earlier, of type showString :: String -> ShowS.
2 10 is the precedence of function application, so a precedence context greater than means that this value is being printed as an argument to some function and thus we need parentheses.
3 We use . to concatenate ShowS values (instead of ++, which is only used for String values).
4 Since the Just constructor looks like a function, we must print the argument to it in a precedence context greater than function application; thus, we pass 11 as the precedence context to showsPrec for whatever comes after the Just.

showsPrec can be thought of as a low-level interface to the capabilities of the Show typeclass. Although the complexity may seem daunting, it is necessary for printing all the possible values that you can define in Haskell.

The last method of the Show typeclass is showList:

-- Give the method a specialized way of showing lists of values.
showList :: [a] -> ShowS

The showList method can be used to override the default of printing lists with square brackets and commas. This is rarely necessary, but is used by the Haskell standard library to print String values using quotes instead of square brackets and to omit the commas.

Exercise 1: Show for lists

Consider the following linked list data type, isomorphic to Haskell’s [a]:

data List a = Nil | Cons a (List a)

Implement the Show typeclass for List a, provided that a implements Show. To do so, fill in the following template:

instance Show a => Show (List a) where
  show xs = ...

This code has another example of a context, this time used in an instance instead of a class declaration. The context Show a with the instance head Show [a] says that for any type a that implements Show, [a] implements Show (with the implementation provided below).

Your implementation of show should act identically to show for Haskell lists, but use {} instead of []. For example, show Nil should be {} and show (Cons 'X' (Cons 'Y' Nil)) should be {'X', 'Y'}.

Exercise 2: showList for Characters

Recall the data type and Show instance we defined earlier:

data ABC = A | B | C | Other

instance Show ABC where
  show A = "A"
  show B = "B"
  show C = "C"
  show Other = "<Not printable>"

Modify this instance to use showsPrec. You can use showString to do so.

Once you have rewritten this instance to use showsPrec, add an implementation for showList to it such that lists of ABC values are printed surrounded by vertical bars, without commas, and skipping Other values. For example, you should have showList [A, B, C, Other, C, B] return a string containing |ABCCB|.

Read

The opposite of the Show typeclass is the Read typeclass. While Show is used to convert Haskell data structures to Strings, Read provides methods to parse Strings into Haskell data structures. Since converting Strings to data structures requires fairly complex parsing, the methods of the Read typeclass are actually almost never used. However, the Haskell Prelude provides the read function:

read :: Read a => String -> a

This is not a method of the Read typeclass, but it requires that the type that’s being output implements Read. To use read, just pass it a String, as in the following example:

Read1.hs
value :: Int
value = read "100"

main :: IO ()
main = print value

Since the output of read is a type variable, it is polymorphic in its output. This can often cause problems, as GHC’s type inference engine will be unable to determine exactly what type is meant to be read. For example, compiling the following program will yield an error, complaining that the type variable a is ambiguous:

main :: IO ()
main = print $ read "100"

The type of read "100" could be Int, Float, Bool, or anything else, and this program would typecheck just fine. If the value is unable to be parsed, the error will happen at runtime, not at compile time. In order to avoid the ambiguous type, you can annotate the read expression with an explicit type:

main :: IO ()
main = print (read "100" :: Int)

This program should compile fine, and, when run, will print 100 to the console.

Reading Safely with readMay

If you try to read a string that isn’t valid, you’ll get a runtime error. For example, the following program will fail:

main = print (read "True" :: Int)

The error message will complain about not being able to find a parse:

*** Exception: Prelude.read: no parse

By using read, we’ve introduced a potential error which is not represented in any way in the type of read; in fact, the purpose of the type system is to eliminate errors like this! To avoid this, you can use the readMay function from the Safe module (from the package safe made for Safe Haskell):

readMay :: String -> Maybe a

Instead of erroring and crashing like read does, readMay will return Nothing if it fails, and Just the result if it succeeds. Using readMay can introduce a bit of complexity into your code base due to the overhead of managing errors, but makes your code typesafe and avoids unexpected crashes, yielding a very robust code base. In general, favor uses of readMay over read whenever possible.

For most programmers, knowing how to use read is enough; however, there may be a time where you need to write a custom implementation of the Read typeclass for one of your own data types. Writing a Read parser is a fairly complex task that requires a little more background than we are assuming in this guide, so we will delay that topic until the guide about parsing.

Enum

Haskell has a special syntax for enumerated lists. For example, when working with integers, all of the following lists are valid:

-- A list of integers between 1 and 10, inclusive on both sides.
small :: [Int]
small = [1..10]

-- A list of odd integers between 1 and 10 (inclusive).
smallOdd :: [1,3..10]

-- An infinite list of all positive integers.
positives :: [Int]
positives = [1..]

-- An infinite list of even positive integers.
positiveEven :: [Int]
positiveEven = [2, 4..]

This syntax is very commonly used with integers; however, it also works with Char and Float values:

-- The list containing 0.0, 0.1, 0.2, and so on until 1.0.
tenths :: [Float]
tenths = [0.0, 0.1 .. 1.0]

-- All lowercase English letters.
lowercase :: [Char]
lowercase = ['a'..'z']

This general syntax is enabled by the Enum typeclass. The Enum typeclass has a whole suite of methods which describe any type that can be enumerated:

class Enum a where
  -- Compute the next element.
  succ :: a -> a
  -- Compute the previous element.
  pred :: a -> a

  -- Convert between integers and our enumerable type.
  toEnum :: Int -> a
  fromEnum :: a -> Int

  -- Functions that the list syntax desugars to.
  enumFromTo :: a -> a -> [a]
  enumFromThenTo :: a -> a -> a -> [a]
  enumFrom :: a -> [a]
  enumFromThen :: a -> a -> [a]

The last four methods that start with enumFrom are used to produce the list syntax above. The four types of list syntax are translate directly into those methods:

[1..10]     ==  enumFromTo 1 10
[1,3..10]   ==  enumFromThenTo 1 3 10
[1..]       ==  enumFrom 1
[2, 4..]    ==  enumFromThen 2 4

Thus, if you implement the Enum typeclass for your own types, you can use this list syntax as well.

In addition the list syntax, the Enum typeclass has functions for using the implementing type as an enumeration. In particular, the fromEnum and toEnum functions can be used convert between the enumerated type and the positive integers. (For example, in the context of ASCII characters, fromEnum gets the ASCII code of a character while toEnum converts it back to a Char.) Also, succ should yield the next element of the enumeration, while pred should yield the predecessor (so, for numeric types, succ should add one and pred should subtract one).

Enum has many methods, but the only ones that are necessary in order to complete an instance definition are fromEnum and toEnum. For instance, if we had a type that could only represent the characters X, Y, Z, or W, we could make it enumerated as follows:

data Var = X | Y | Z | W

instance Enum Var where
  fromEnum X = 0
  fromEnum Y = 1
  fromEnum Z = 2
  fromEnum W = 3

  toEnum 0 = X
  toEnum 1 = Y
  toEnum 2 = Z
  toEnum 3 = W
  toEnum _ = error "Invalid value"

We can then use any methods of the Enum class, including the syntactic sugar for list ranges. For example, we could write [X .. W], which evaluates to [X, Y, Z, W]. (The spaces around the dots are syntactically important; without them the parser gets confused.)

Bounded

The Bounded typeclass is the simplest of all of the typeclasses discussed in this section:

class Bounded a where
  -- A lower bound on all values.
  minBound :: a

  -- An upper bound on all values.
  maxBound :: a

Strangely enough, though, Ord is not required by Bounded, even though Bounded is making a statement about the ordering of values. This is because Bounded only requires that minBound is less than all elements and maxBound is greater than all elements; however, if Bounded a required Ord a, then it would also require there to be a total order on all the possible values of type a. (For example, if there are two values x, y :: a, and both are above minBound and below maxBound. In that case, you could implement Bounded, even if the expression compare x y made no sense because x and y themselves were not directly comparable.)

For any type a which is both Bounded and Enum, the Enum methods should respect the bounds. For instance, succ maxBound and pred minBound should both result in runtime errors, since there should be nothing outside of those bounds. Similarly, enumFrom and enumFromThen should not go beyond (above or below) the maxBound or minBound set by the Bounded instance.

Deriving Typeclasses

Many of the typeclasses you encounter in Haskell are fairly simple and have very routine implementations. For example, consider the following data structure and Show instance:

data Something a = A | B | C | D | E a

instance Show a => Show (Something a) where
  show A = "A"
  show B = "B"
  show C = "C"
  show D = "D"
  show (E a) = "(E " ++ show a ++ ")"

instance Eq a => Eq (Something a) where
  A == A = True
  B == B = True
  C == C = True
  D == D = True
  (E x) == (E y) = x == y

Instances like the one above are filled with boilerplate code. They require a lot of typing, provide plenty of room for error, and are completely and thoroughly uninteresting. To avoid having to declare these boring instances for every data type you create, Haskell can auto-generate these instances if you ask it to using the deriving keyword. The following declaration demonstrates a use of the deriving keyword:

data Something a = A | B| C | D | E a
  deriving (Show, Eq) (1)
1 If you only want to derive one typeclass, you don’t need the parentheses or commas; for instance, if you didn’t want Something a to be comparable using ==, you could just write deriving Show instead of deriving (Show, Eq).

Haskell can automatically derive typeclass instances for many common typeclasses, such as Eq, Ord, Bounded, Read, Enum, and Show. (Some more complex typeclasses can also be derived, but sometimes it requires extra language extensions.) You should generally let Haskell derive all your simple typeclass instances for you, unless you need a behaviour that differs from the default. The default behaviours are usually pretty intuitive – for example, Read and Show parse and output their data structures just like you would in code, and Enum and Bounded use the order of constructors to order their values.

Numeric Typeclasses

Typeclasses are fundamental to the way that Haskell handles numbers. Haskell has about a half-dozen different numeric types (and more provided by libraries), and then divides functions operating on those types among a half-dozen different numeric typeclasses. When you write numerical code, then, you use whatever functions you need and choose the numeric typeclasses they require. The code you write then works for all possible applicable numeric types.

Some of the commonly used Haskell types are the following:

  • Float: A standard IEEE 32-bit floating point number.

  • Double: A standard IEEE 64-bit floating point number.

  • Rational: A rational number represented as a fraction with arbitrary precision integer numerators and denominators.

  • Integer: An arbitrary precision integer.

  • Int: A 29-bit machine integer.

  • CFloat, CInt, CDouble, etc: Types used for communicating with C libraries through the foreign function interface (FFI).

Next, we look at the typeclasses that provide the functions that let us operate on these functions. The most common and base typeclass is Num:

class Num a where (1)
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
1 Common types with Num instances include Float, Double, Rational, Integer, Int, CFloat, CInt, CDouble, and Complex a (from Data.Complex) for any type a with a RealFloat instance (discussed later).

The binary operators +, *, and - do exactly what you expect. negate multiplies a number by negative one (or otherwise negates it); abs takes the absolute value of the number; signum returns either positive or negative one, depending on the sign of its argument. Finally, fromInteger can be used to convert from any arbitrary precision integer to another type of Num. Whenever you need to convert an Integer to any other numeric type, use fromInteger.

The Num typeclass only includes +, *, and -; it does not include / (division), because some numeric types do not support standard division. For example, you cannot divide two Int values to get another Int value without some sort of rounding. In order to support rounding, a numeric type must implement the Fractional typeclass:

class Num a => Fractional a where (1)
  (/) :: a -> a -> a
  recip :: a -> a
  fromRational :: Rational -> a
1 Common types with Fractional instances include Float, Double, Rational, CDouble, CFloat,, and Complex a (from Data.Complex) for any type a with a RealFloat instance.

The Fractional typeclass has Num as a superclass (it requires Num a in order to implement Fractional a). Fractional allows you to divide using / and take the reciprocal of a number using recip. Finally, just like we can use fromInteger to convert arbitrary precision integers to Num values, we can use fromRational to convert from arbitrary precision fractions (ratios of arbitrary precision integers) to any Fractional type.

Like Fractional supports numbers that can do division, Integral supports various integer operations:

class (Real a, Enum a) => Integral a where (1)
  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
1 Common types with Integral instances include Int, Integer, CInt, and CLong.

The meanings of these should be somewhat self-explanatory. quot takes the quotient of two numbers, rem takes the remainder, div does integer division (truncated towards negative infinity), mod takes the integer modulus. quotRem and divMod do quot/rem and div/mod together. Finally, toInteger converts from an Integral type to an Integer, since Integer is meant to be the "most general" integer type.

The Integral typeclass has two superclasses: Real and Enum. We’ve already seen Enum in the previous section. The Real typeclass provides only toRational, which returns an exact fraction that represents the Real value:

class (Num a, Ord a) => Real a where(1)
  toRational :: a -> Rational
1 Types with instances of the Real typeclass include Float, Double, Int, Integer, Rational, CInt, and CFloat, as well as many other native types that start with C (such as CLong).

The Real typeclass has Num and Ord as a superclass, so by transitivity Integral also requires both of these.

For real numbers that are represented as floating point numbers, Haskell provides the Floating typeclass which provides all the traditional transcendental functions:

class Fractional a => Floating a where (1)
  pi :: a
  exp :: a -> a
  sqrt :: a -> a
  log :: a -> a
  (**) :: a -> a -> a
  logBase :: a -> a -> a
  sin :: a -> a
  tan :: a -> a
  cos :: a -> a
  asin :: a -> a
  atan :: a -> a
  acos :: a -> a
  sinh :: a -> a
  tanh :: a -> a
  cosh :: a -> a
  asinh :: a -> a
  atanh :: a -> a
  acosh :: a -> a
1 Types with instances of Floating include the standard floating point types, Float and Double, the foreign interface floating types CFloat and CDouble, and complex numbers with real floating coefficients, Complex a with a having an instance of RealFloat.

Finally, two more typeclasses provide the rest of the kitchen sink of numeric functions. For types that implement Real and Fractional, there is the RealFrac typeclass; for types that implement Real and Floating, there is the RealFloat typeclass:

-- Real and Fractional.
class (Real a, Fractional a) => RealFrac a where (1)
  properFraction :: Integral b => a -> (b, a)
  truncate :: Integral b => a -> b
  round :: Integral b => a -> b
  ceiling :: Integral b => a -> b
  floor :: Integral b => a -> b

-- Real and floating.
-- Functions for dealing with IEEE floating point numbers.
class (RealFrac a, Floating a) => RealFloat a where (2)
  floatRadix :: a -> Integer
  floatDigits :: a -> Int
  floatRange :: a -> (Int, Int)
  decodeFloat :: a -> (Integer, Int)
  encodeFloat :: Integer -> Int -> a
  exponent :: a -> Int
  significand :: a -> a
  scaleFloat :: Int -> a -> a
  isNaN :: a -> Bool
  isInfinite :: a -> Bool
  isDenormalized :: a -> Bool
  isNegativeZero :: a -> Bool
  isIEEE :: a -> Bool
  atan2 :: a -> a -> a
1 As you may expect, members of the RealFrac class include Float, Double, CFloat, CDouble, and Rational.
2 As you may expect, members of the RealFloat class include Float, Double, CFloat, CDouble; using RealFloat for other types is rare in practice.

Converting Between Numeric Types

Due to Haskell’s rigid type system, you cannot arbitrarily cast between numeric types. Sometimes, it may not immediately be clear how to convert one numeric type to another. The following table lists functions that can be used to convert between common types:

Float Double Int Integer Rational

Float

id

realToFrac

round

round

toRational

Double

realToFrac

id

round

round

toRational

Int

fromIntegral

fromIntegral

id

fromIntegral

fromIntegral

Integer

fromIntegral

fromIntegral

fromIntegral

id

fromIntegral

Rational

fromRational

fromRational

round

round

id

Instead of using round, you could also use floor or ceiling, depending on the behaviour you wanted.

Literals

Some tripping points for starting Haskell programmers are errors due to type inference for numeric literals.

For example, suppose you write your own typeclass for displaying pretty-printed values:

-- | Display a value prettily.
class Pretty a where
  pretty :: a -> String

For simple Int values, you would like to have the same output as show, so you write an instance for Pretty Int:

-- | Integers are already pretty.
instance Pretty Int where
  pretty = show

Finally, you’d like to test your instance, so you go ahead and write a quick test:

main :: IO ()
main =  print (pretty 3)

GHC will refuse to compile this and spit out an error along the following lines:

No instance for (Pretty a) arising from a use of ‘pretty’
The type variable ‘a’ is ambiguous

Note: there is a potential instance available:
    instance Pretty Int

In the first argument of ‘print’, namely ‘(pretty 3)’
In the expression: print (pretty 3)
In an equation for ‘main’: main = print (pretty 3)


No instance for (Num a) arising from the literal ‘3’
The type variable ‘a’ is ambiguous
In the first argument of ‘pretty’, namely ‘3’
In the first argument of ‘print’, namely ‘(pretty 3)’
In the expression: print (pretty 3)

GHC cannot infer the type that you want for the literal value "3"! Numeric literals obey the following rules:

  • If the literal is an integer, it is converted into an Integer by the compiler, and then the compiler inserts a fromInteger call to convert the literal into any type that implements Num. Thus, the type of a numeric integer literal such as 3 is Num a => a.

  • If the literal is a fraction or floating numeral (with a decimal point), then the compiler converts it to a ratio of Integer values (a Rational) and then uses fromRational to convert to any type with a Fractional instance. Thus, the type of a numeric floating point literal such as 3.5 is Floating a => a.

In the example program above, our main function is print (pretty 3). The compiler knows that 3 is some Num type; from the use of pretty, it infers that it must also be some Pretty type. However, it does not know for sure which type it should be, and thus the program is ambiguous and GHC rejects it with an error.

When GHC does type inference with type classes, it must find a set of types that satisfy all the typeclass constraints. In this case, it must find some type a which satisfies the constraint (Pretty a, Num a). It is very important to remember, however, GHC does not search for solutions to typeclass constraints. Just because a potential solution to the constraints exists (in this case, the literal could be Int) does not mean that GHC will assume you meant that solution, even if there is only one candidate instance. (This is because typeclasses can be extended at any time, so if some module your program imported added more typeclass instances, your program would suddenly have a different behaviour, or would not compile.)

Thus, given the information that the compiler has, it simply cannot make a decision about what type you intend the literal 3 to be. You can fix this by explicitly telling it the type:

main :: IO ()
main = print $ pretty (3 :: Int)

Defaulting rules

In the previous section, we discussed the following faulty program:

-- | Display a value prettily.
class Pretty a where
  pretty :: a -> String

-- | Integers are already pretty.
instance Pretty Int where
  pretty = show

main :: IO ()
main =  print (pretty 3)

Compiling this program yielded an error, as the compiler could not determine what type the literal 3 should be. However, consider the following program:

main :: IO ()
main =  print (show 3)

If you try compiling this, you will find that even though show has almost the same type signature as pretty, this program compiles just fine! The compiler does not issue any errors, and does exactly what you would expect it to do.

The secret behind this mysterious behaviour is called defaulting. Defaulting is a small addition to the Haskell language that eliminates these type ambiguity errors in the most common cases. Although defaulting is certainly somewhat of a hack, it’s necessary in order to make working with numbers remotely convenient; without it, entering something like 1 + 1 into an interactive prompt would result in a type ambiguity error.

Defaulting allows the compiler to assume some "default" type whenever it encounters an otherwise ambiguous type (C1 a, C2 a, C3 a, …​) => a. In standard Haskell, defaulting kicks in if and only if the following conditions are met:

  • There are no other constraints on the type variable a.

  • All the classes C1, C2, and so on are standard classes from the Prelude.

  • At least one of the clases C1, C2, and so on is numeric (Num, Fractional, etc).

When defaulting kicks in, it looks for a default declaration. Default declarations look like this:

default (Int, Double, Float, Integer, Rational)

The compiler will look through this list left to right and find the first type in the list that matches all the required constraints. That type will then be inferred for the type variable.

Most Haskell programs do not include their own default statements. When no default declaration is present, the compiler assumes the following defaulting order:

-- Default defaulting rules, if no others are present.
default (Integer, Double)

Defaulting may be completely turned off with default (); however, note that a default declaration’s effects are only limited to the module in which it was declared.

GHCi (the interactive Haskell interpreter that ships with GHC) extends these rules somewhat. When at an interactive prompt, defaulting kicks in if and only if the following (modified from before) conditions are met:

  • There are no other constraints on the type variable a.

  • All the classes C1, C2, and so on are single-parameter classes.

  • At least one of the clases C1, C2, and so on is numeric (Num, Fractional, etc) or is Show, Eq, or Ord.

With these relaxations of the rules, code such as show (reverse []) will work at the interactive prompt. However, if you try to compile the following program, you will get an ambiguity error:

main :: IO ()
main = print (reverse [])

The GHCi rules may be turned on by enabling the ExtendedDefaultRules extension.