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.
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.
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. |
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:
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.
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:
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.
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 |
|
|
|
|
|
Double |
|
|
|
|
|
Int |
|
|
|
|
|
Integer |
|
|
|
|
|
Rational |
|
|
|
|
|
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 afromInteger
call to convert the literal into any type that implementsNum
. Thus, the type of a numeric integer literal such as3
isNum 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 (aRational
) and then usesfromRational
to convert to any type with aFractional
instance. Thus, the type of a numeric floating point literal such as3.5
isFloating 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 isShow
,Eq
, orOrd
.
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.