Table of Contents
Typeclasses are one of the most powerful features in Haskell. They allow you to define generic interfaces that provide a common feature set over a wide variety of types. Typeclasses are at the heart of some basic language features such as equality testing and numeric operators. Before we talk about what exactly typeclasses are, though, we'd like to explain the need for them.
Let's imagine that for some unfathomable reason, the designers of the
Haskell language neglected to implement the equality test
==. Once you got over your shock at hearing this,
you resolved to implement your own equality tests. Your application
consisted of a simple Color type, and so your first
equality test is for this type. Your first attempt might look like
this:
data Color = Red | Green | Blue colorEq :: Color -> Color -> Bool colorEq Red Red = True colorEq Green Green = True colorEq Blue Blue = True colorEq _ _ = False
ghci>:l naiveeq.hs[1 of 1] Compiling Main ( naiveeq.hs, interpreted ) Ok, modules loaded: Main.ghci>colorEq Red RedTrueghci>colorEq Red GreenFalse
Now, let's say that you want to add an equality test for
Strings.
Since a Haskell String is a list of characters, we can
write a simple function to perform that test.
For simplicity, we
cheat a bit and use the == operator here to
illustrate.
stringEq :: [Char] -> [Char] -> Bool -- Match if both are empty stringEq [] [] = True -- If both start with the same char, check the rest stringEq (x:xs) (y:ys) = x == y && stringEq xs ys -- Everything else doesn't match stringEq _ _ = False
You should now be able to see a problem: we have to use a function
with a different name for every different type that we want to be able to compare.
That's inefficient and annoying. It's much more convenient to be able
to just use == to compare anything. It may also
be useful to write generic functions such as /=
that could be implemented in terms of ==, and
valid for almost anything. By having a generic function that
can compare anything, we can also make our code generic: if a
piece of code only needs to compare things, then it ought to be
able to accept any data type that the compiler knows how to
compare. And, what's more, if new data types are added later,
the existing code shouldn't have to be modified.
Haskell's typeclasses are designed to address all of these things.
Typeclasses define a set of functions that can have different implementations depending on the type of data they are given. Typeclasses may look like objects, but they are truly quite different.
Let's use typeclasses to solve our equality dilemma from earlier in the
chapter. To begin with, we must define the typeclass
itself. We want a function that takes two parameters, both the
same type, and returns a Bool indicating whether or not
they are equal. We don't care what that type is, but we just want two
items of that type. Here's our first definition of a typeclass:
class BasicEq a where
isEqual :: a -> a -> Bool
This says that we are declaring a typeclass named
BasicEq, and we'll refer to instance types with the
letter a. An instance type of this typeeclass
is any type that implements the functions defined in the typeclass.
This typeclass defines one function.
That function takes two parameters—both corresponding to instance
types—and returns a Bool.
On the first line, the name of the parameter a
was chosen arbitrarily. We could have used any name. The key is that,
when you list the types of your functions, you must use that name to
refer to instance types.
Let's look at this in ghci.
Recall that you
can type :t in ghci to
have it show you the type of something. Let's see what it says about
isEqual:
*Main> :t isEqual
isEqual :: (BasicEq a) => a -> a -> Bool
You can read that this way: "For all types a, so
long as a is an instance of
BasicEq, isEqual takes two
parameters of type a and returns a
Bool". Let's take a quick look at defining
isEqual for a particular type.
instance BasicEq Bool where
isEqual True True = True
isEqual False False = True
isEqual _ _ = False
You can also use ghci to verify that we can now use
isEqual on Bools, but not on any other type:
ghci>:l eqclasses.hs[1 of 1] Compiling Main ( eqclasses.hs, interpreted ) Ok, modules loaded: Main.ghci>isEqual False FalseTrueghci>isEqual False TrueFalseghci>isEqual "Hi" "Hi"<interactive>:1:0: No instance for (BasicEq [Char]) arising from a use of `isEqual' at <interactive>:1:0-16 Possible fix: add an instance declaration for (BasicEq [Char]) In the expression: isEqual "Hi" "Hi" In the definition of `it': it = isEqual "Hi" "Hi"
Notice that when we tried to compare two strings, ghci noticed that
we hadn't provided an instance of BasicEq for
String. It therefore didn't know how to compare a String, and
suggested that we could fix the problem by defining an instance of
BasicEq for [Char], which is the
same as String.
We'll go into more detail on defining instances in the section called “Declaring typeclass instances”. First, though, let's continue to look at ways to define typeclasses. In this example, a not-equal-to function might be useful. Here's what we might say to define a typeclass with two functions:
class BasicEq2 a where
isEqual2 :: a -> a -> Bool
isNotEqual2 :: a -> a -> Bool
Someone providing an instance of BasicEq2 will
be required to define two functions: isEqual2 and
isNotEqual2.
While our definition of BasicEq2 is fine, it seems
that we're making extra work for ourselves.
Logically speaking, for any type, if we know the return value of
isEqual, we also know the return value of
isNotEqual, and vice-versa. The definition of
BasicEq2 requires programmers to define both
functions for every instance of BasicEq2. It would
be nice to only require programmers to write one function, and have the
system figure out the proper return value of the other automatically.
Logically speaking, if we
know what isEqual or
isNotEqual would return, we know how to figure out
what the other function would return, for all types. Rather than
making users of the typeclass define both functions for all types, we
can provide default implementations for them. Then, users will only
have to implement one function.
[7]
Here's an example that shows how to do
this.
class BasicEq3 a where
isEqual3 :: a -> a -> Bool
isEqual3 x y = not (isNotEqual3 x y)
isNotEqual3 :: a -> a -> Bool
isNotEqual3 x y = not (isEqual3 x y)People implementing this class must provide an implementation of at least one function. They can implement both if they wish, but they will not be required to. While we did provide defaults for both functions, each function depends on the presence of the other to calculate an answer. If we don't specify at least one, the resulting code would be an endless loop. Therefore, at least one function must always be implemented.
With BasicEq3, we have provided a class that does
very much the same thing as Haskell's built-in ==
and /= operators. In fact, these operators are
defined by a typeclass that looks almost identical to
BasicEq3. The Haskell 98 Report
defines a typeclass that implements equality
comparison. Here is the code for the built-in
Eq typeclass.
Eq typeclass Note how similar it is to our
BasicEq3 typeclass.
class Eq a where
(==), (/=) :: a -> a -> Bool
-- Minimal complete definition:
-- (==) or (/=)
x /= y = not (x == y)
x == y = not (x /= y)
Now that you know how to define typeclasses, it's time to learn how to define instances of typeclasses. Recall that types are made instances of a particular typeclass by implementing the functions necessary for that typeclass.
FIXME: rearrange? see comments
Recall our attempt to create a test for equality over a
Color type back in the section called “The need for typeclasses”.
Now let's see how we could make that same Color
type a member of the BasicEq3 class.
instance BasicEq3 Color where
isEqual3 Red Red = True
isEqual3 Green Green = True
isEqual3 Blue Blue = True
isEqual3 _ _ = False
Notice that we provide essentially the same function as we used
back in the section called “The need for typeclasses”. In fact, the
implementation is identical. However, in this case, we can use
isEqual3 on any type that
we declare is an instance of BasicEq3, not just
this one color type. We could define equality tests for anything
from numbers to graphics using the same basic pattern. In fact, as you
will see in the section called “Equality, Ordering, and Comparisons”, this
is exactly how you can make Haskell's == operator
work for your own custom types.
Note also that the BasicEq3 class defined both
isEqual3 and isNotEqual3, but we
implemented only one of them in the Color instance.
That's because of the default implementation
contained in BasicEq3. Since we didn't explicitly
define isNotEqual3, the compiler automatically uses
the default implementation given in the BasicEq3
declaration.
Now that we've discussed defining your own typeclasses and making your types instances of typeclasses, it's time to introduce you to typeclasses that are a standard part of the Haskell Prelude. As we mentioned at the beginning of this chapter, typeclasses are at the core of some important aspects of the language. We'll cover the most common ones here. For more details, the Haskell library reference is a good resource. It will give you a description of the typeclasses, and usually also will tell you which functions you must implement to have a complete definition. FIXME: add link to lib ref
The Show typeclass is used to convert values to
Strings. It is perhaps most commonly used to
convert numbers to Strings, but it is defined for
so many types that it can be used to convert quite a bit more.
If you have defined your own types, making them instances of
Show will make it easy to display them in ghci
or print them out in programs.
The most important function of Show is
show. It takes one argument: the data to convert.
It returns a String representing that data.
ghci reports the type of show like this:
ghci>:t showshow :: (Show a) => a -> String
Let's look at some examples of converting values to strings:
ghci>show 1"1"ghci>show [1, 2, 3]"[1,2,3]"ghci>show (1, 2)"(1,2)"
Remember that ghci displays results as they would
be entered into a Haskell program. So the expression show 1
returns a single-character string containing the digit
1. That is, the quotes are not part of the string
itself. We can make that clear by using
putStrLn:
ghci>putStrLn (show 1)1ghci>putStrLn (show [1,2,3])[1,2,3]
You can also use show on
Strings:
ghci>show "Hello!""\"Hello!\""ghci>putStrLn (show "Hello!")"Hello!"ghci>show ['H', 'i']"\"Hi\""ghci>putStrLn (show "Hi")"Hi"ghci>show "Hi, \"Jane\"""\"Hi, \\\"Jane\\\"\""ghci>putStrLn (show "Hi, \"Jane\"")"Hi, \"Jane\""
Running show on Strings can be
confusing. Since show generates a result that
is suitable for a Haskell literal, show adds
quotes and escaping suitable for inclusion in a Haskell program.
ghci also uses show to
display results, so quotes and escaping get added twice. Using
putStrLn can help make this difference clear.
You can define a Show instance for your own types
easily. Here's an example:
instance Show Color where
show Red = "Red"
show Green = "Green"
show Blue = "Blue"
This example defines an instance of Show for our
type
Color (see the section called “The need for typeclasses”). The implementation is
simple: we define a function show and that's all
that's needed.
The Read typeclass is essentially the opposite of Show: it
defines functions that will
take a String, parse it, and return data in a native Haskell type.
The most useful function in Read is read.
You can ask ghci for its type like this:
ghci>:t readread :: (Read a) => String -> a
Here's an example illustrating the use of read and show:
main = do
putStrLn "Please enter a Double:"
inpStr <- getLine
let inpDouble = (read inpStr)::Double
putStrLn ("Twice " ++ show inpDouble ++ " is " ++ show (inpDouble * 2))FIXME: have we already explained main, do, and type annotations on expressions?
This is a simple example of read and show together. Notice that
we gave an explicit type of Double when processing the read.
That's because read returns a value of type
Read a => a and show expects a value of type
Show a => a. There are many types that have
instances
defined for both Read and Show. Without knowing a specific type,
the compiler must guess from these many types which one is
needed. In situations like this, it may often choose Integer. If
we wanted to accept floating-point input, this wouldn't work, so we
provided an explicit type.
You can see the same effect at work if you try to use read on the
ghci command line. ghci internally uses show to display
results, meaning that you can hit this ambiguous typing problem there
as well. You'll need to explicitly give types for
your read results in
ghci as shown here:
ghci>read "5"<interactive>:1:0: Ambiguous type variable `a' in the constraint: `Read a' arising from a use of `read' at <interactive>:1:0-7 Probable fix: add a type signature that fixes these type variable(s)ghci>:t (read "5")(read "5") :: (Read a) => aghci>(read "5")::Integer5ghci>(read "5")::Double5.0
Recall the type of read:
(Read a) => String -> a. The
a here is the type of each instance of Read.
Which particular parsing function is called depends upon the type
that is expected from the return value of read. Let's see how that
works:
ghci>(read "5.0")::Double5.0ghci>(read "5.0")::Integer*** Exception: Prelude.read: no parse
Notice the error when trying to parse 5.0 as an
Integer. The interpreter selected a different instance of Read
when the return value was expected to be Integer than it did when a
Double was expected. The Integer parser doesn't accept decimal
points, and caused an exception to be raised.
The Read class provides for some fairly complicated parsers.
You can define a simple parser by providing an implementation for the
readsPrec function. Your implementation can
return a list containing exactly one tuple on a successful parse, or
an empty list on an unsuccessful parse. Here's an example
implementation:
instance Read Color where
-- readsPrec is the main function for parsing input
readsPrec _ value =
-- We pass tryParse a list of pairs. Each pair has a string
-- and the desired return value. tryParse will try to match
-- the input to one of these strings.
tryParse [("Red", Red), ("Green", Green), ("Blue", Blue)]
where tryParse [] = [] -- If there is nothing left to try, fail
tryParse ((attempt, result):xs) =
-- Compare the start of the string to be parsed to the
-- text we are looking for.
if (take (length attempt) value) == attempt
-- If we have a match, return the result and the
-- remaining input
then [(result, drop (length attempt) value)]
-- If we don't have a match, try the next pair
-- in the list of attempts.
else tryParse xs
This example handles the known cases for the three colors. It
returns an empty list (resulting in a "no parse" message) for others.
The function is supposed to return the part of the input that was not
parsed, so that the system can integrate the parsing of different
types together. Here's an example of using this new instance of
Read:
ghci>(read "Red")::ColorRedghci>(read "Green")::ColorGreenghci>(read "Blue")::ColorBlueghci>(read "[Red]")::[Color][Red]ghci>(read "[Red,Red,Blue]")::[Color][Red,Red,Blue]ghci>(read "[Red, Red, Blue]")::[Color]*** Exception: Prelude.read: no parse
Notice the error on the final attempt. That's because our parser is
not smart enough to handle leading spaces yet. If we modified it to
accept leading spaces, that attempt would work. You could
rectify this by modifying your Read instance to discard any
leading spaces, which is common practice in Haskell programs.
![]() | Tip |
|---|---|
While it is possible to build sophisticated parsers using
the |
You may often have a data structure in memory that you need to store on disk for later retrieval or to send across the network. The process of converting data in memory to a flat series of bits for storage is called serialization.
It turns out that read and show make excellent tools for
serialization. show produces output that is both human-readable and
machine-readable. Most show output is also syntactically-valid
Haskell, though it is up to people that write Show instances to
make it so.
![]() | Tip |
|---|---|
String handling in Haskell is normally lazy, so |
ghci>let d1 = [Just 5, Nothing, Nothing, Just 8, Just 9]::[Maybe Int]ghci>putStrLn (show d1)[Just 5,Nothing,Nothing,Just 8,Just 9]ghci>writeFile "/tmp/test" (show d1)
First, we assign d1 to be a list. Next, we print
out the result of show d1 so we can see what it
generates. Then, we write the result of show d1
to a file named /tmp/test.
Let's try reading it back. FIXME: xref to explanation of variable binding in ghci
ghci>input <- readFile "/tmp/test""[Just 5,Nothing,Nothing,Just 8,Just 9]"ghci>let d2 = read input<interactive>:1:9: Ambiguous type variable `a' in the constraint: `Read a' arising from a use of `read' at <interactive>:1:9-18 Probable fix: add a type signature that fixes these type variable(s)ghci>let d2 = (read input)::[Maybe Int]ghci>print d1[Just 5,Nothing,Nothing,Just 8,Just 9]ghci>print d2[Just 5,Nothing,Nothing,Just 8,Just 9]ghci>d1 == d2True
First, we ask Haskell to read the file back.[8] Then,
we try to assign the result of read input to
d2. That generates an error. The reason is that
the interpreter doesn't know what type d2 is meant
to be, so it doesn't know how to parse the input. If we give it an
explicit type, it works, and we can verify that the two sets of data
are equal.
Since so many different types are instances of Read and Show by
default (and others can be made instances easily; see the section called “Automatic Derivation”), you can use it for
some really complex data structures. Here are a few examples of
slightly more complex data structures:
FIXME: like to def of $, or explain it here
ghci>putStrLn $ show [("hi", 1), ("there", 3)][("hi",1),("there",3)]ghci>putStrLn $ show [[1, 2, 3], [], [4, 0, 1], [], [503]][[1,2,3],[],[4,0,1],[],[503]]ghci>putStrLn $ show [Left 5, Right "three", Left 0, Right "nine"][Left 5,Right "three",Left 0,Right "nine"]ghci>putStrLn $ show [Left 0, Right [1, 2, 3], Left 5, Right []][Left 0,Right [1,2,3],Left 5,Right []]
FIXME: some of these tables don't render well under sgml2x. Will need to verify that they look good under the O'Reilly renderer.
Haskell has a powerful set of numeric types. You can use everything
from fast 32-bit or 64-bit integers to arbitrary-precision rational
numbers. You probably know that operators such as
+ can work with just about all of these. This
feature is implemented using typeclasses. As a side benefit, it
allows you to define your own numeric types and make them first-class
citizens in Haskell.
Let's begin our discussion of the typeclasses surrounding numeric types with an examination of the types themselves. Table 7.1, “Selected Numeric Types” describes the most commonly-used numeric types in Haskell. Note that there are also many more numeric types available for specific purposes such as interfacing to C.
Table 7.1. Selected Numeric Types
| Type | Description |
|---|---|
Double | Double-precision floating point. A common choice for floating-point data. |
Float | Single-precision floating point. Often used when interfacing with C. |
Int | Fixed-precision signed integer; minimum range [-2^29..2^29-1]. Commonly used. |
Int8 | 8-bit signed integer |
Int16 | 16-bit signed integer |
Int32 | 32-bit signed integer |
Int64 | 64-bit signed integer |
Integer | Arbitrary-precision signed integer; range limited only by machine resources. Commonly used. |
Rational | Arbitrary-precision rational numbers. Stored as a
ratio of two Integers. |
Word | Fixed-precision unsigned integer; storage size same as
Int |
Word8 | 8-bit unsigned integer |
Word16 | 16-bit unsigned integer |
Word32 | 32-bit unsigned integer |
Word64 | 64-bit unsigned integer |
These are quite a few different numeric types. There are some
operations, such as addition, that ought to work with all of them.
There are others, such as asin, that only apply to
floating-point types. Table 7.2, “Selected Numeric Functions and Constants”
summarizes the different functions that operate on numeric types,
and
Table 7.3, “Typeclass Instances for Numeric Types” matches the types with
their respective typeclasses. As you read that table, keep in mind
that Haskell operators are just functions: you can say either
(+) 2 3 or 2 + 3 with the same
result. By convention, when referring to an operator as a function,
it is written in parenthesis as seen in this table.
Table 7.2. Selected Numeric Functions and Constants
| Item | Type | Module | Description |
|---|---|---|---|
(+) | Num a => a -> a -> a | Prelude | Addition |
(-) | Num a => a -> a -> a | Prelude | Subtraction |
(*) | Num a => a -> a -> a | Prelude | Multiplication |
(/) | Fractional a => a -> a -> a | Prelude | Fractional division |
(**) | Floating a => a -> a -> a | Prelude | Raise to the power of |
(^) | (Num a, Integral b) => a -> b -> a | Prelude | Raise a number to a non-negative, integral power |
(^^) | (Fractional a, Integral b) => a -> b ->
a | Prelude | Raise a fractional number to any integral power |
(%) | Integral a => a -> a -> Ratio a | Data.Ratio | Ratio composition |
(.&.) | Bits a => a -> a -> a | Data.Bits | Bitwise and |
(.|.) | Bits a => a -> a -> a | Data.Bits | Bitwise or |
abs | Num a => a -> a | Prelude | Absolute value |
approxRational | RealFrac a => a -> a ->
Rational | Data.Ratio | Approximate rational composition based on fractional numerators and denominators |
cos | Floating a => a -> a | Prelude | Cosine. Also provided are acos,
cosh, and acosh, with
the same type. |
div | Integral a => a -> a -> a | Prelude | Integer division always truncated down; see also
quot |
fromInteger | Num a => Integer -> a | Prelude | Conversion from an Integer to any numeric type |
fromIntegral | (Integral a, Num b) => a -> b | Prelude | More general conversion from any Integral to
any numeric type |
fromRational | Fractional a => Rational -> a | Prelude | Conversion from a Rational. May be lossy. |
log | Floating a => a -> a | Prelude | Natural logarithm |
logBase | Floating a => a -> a -> a | Prelude | Log with explicit base |
maxBound | Bounded a => a | Prelude | The maximum value of a bounded type |
minBound | Bounded a => a | Prelude | The minimum value of a bounded type |
mod | Integral a => a -> a -> a | Prelude | Integer modulus |
pi | Floating a => a | Prelude | Mathematical constant pi |
quot | Integral a => a -> a -> a | Prelude | Integer division; fractional part of quotient truncated towards zero |
recip | Fractional a => a -> a | Prelude | Reciprocal |
rem | Integral a => a -> a -> a | Prelude | Remainder of integer division |
round | (RealFrac a, Integral b) => a -> b | Prelude | Rounds to nearest integer |
shift | Bits a => a -> Int -> a | Bits | Shift left by the specified number of bits, which may be negative for a right shift. |
sin | Floating a => a -> a | Prelude | Sine. Also provided are asin,
sinh, and asinh, with
the same type. |
sqrt | Floating a => a -> a | Prelude | Square root |
tan | Floating a => a -> a | Prelude | Tangent. Also provided are atan,
tanh, and atanh, with
the same type. |
toInteger | Integral a => a -> Integer | Prelude | Convert any Integral to an Integer |
toRational | Real a => a -> Rational | Prelude | Convert losslessly to Rational |
truncate | (RealFrac a, Integral b) => a -> b | Prelude | Truncates number towards zero |
xor | Bits a => a -> a -> a | Data.Bits | Bitwise exclusive or |
Table 7.3. Typeclass Instances for Numeric Types
| Type | Bits | Bounded | Floating | Fractional | Integral | Num | Real | RealFrac |
|---|---|---|---|---|---|---|---|---|
Double | X | X | X | X | X | |||
Float | X | X | X | X | X | |||
Int | X | X | X | X | X | |||
Int16 | X | X | X | X | X | |||
Int32 | X | X | X | X | X | |||
Int64 | X | X | X | X | X | |||
Integer | X | X | X | X | ||||
Rational or any Ratio | X | X | X | X | ||||
Word | X | X | X | X | X | |||
Word16 | X | X | X | X | X | |||
Word32 | X | X | X | X | X | |||
Word64 | X | X | X | X | X |
Converting between numeric types is another common need. Table 7.2, “Selected Numeric Functions and Constants” listed many functions that can be used for conversion. However, it is not always obvious how to apply them to convert between two arbitrary types. To help you out, Table 7.4, “Conversion Between Numeric Types” provides information on converting between different types.
Table 7.4. Conversion Between Numeric Types
| Source Type | Destination Type | |||
|---|---|---|---|---|
Double, Float | Int, Word | Integer | Rational | |
Double, Float | fromRational . toRational | truncate * | truncate * | toRational |
Int, Word | fromIntegral | fromIntegral | fromIntegral | fromIntegral |
Integer | fromIntegral | fromIntegral | N/A | fromIntegral |
Rational | fromRational | truncate * | truncate * | N/A |
* Instead of truncate, you could also use
round, ceiling, or
floor.
For an extended example demonstrating the use of these numeric typeclasses, see the section called “Extended example: Numeric Types”.
We've already talked about the arithmetic operators such as
+ that can be used for all sorts of different
numbers. But there are some even more widely-applied operators in
Haskell. The most obvious, of course, are the equality tests:
== and /=. These operators are
defined in the Eq class.
There are also comparison operators such as >= and
<=. These are declared by the Ord typeclass.
These are in a separate typeclass because there are some types, such
as Handle, where an equality test makes sense, but there is no way
to express a particular ordering. Anything that is an instance of
Ord can be sorted by Data.List.sort.
Almost all Haskell types are instances of Eq, and nearly as many
are instances of Ord.
For many simple data types, the Haskell compiler can automatically
derive instances of Read, Show, Bounded, Enum, Eq, and Ord
for you.[9]
This saves you the effort of having to manually write code to compare
or display your own types.
data Color = Red | Green | Blue
deriving (Read, Show, Eq, Ord)Let's take a look at how these derived instances work for us:
ghci>show Red"Red"ghci>(read "Red")::ColorRedghci>(read "[Red,Red,Blue]")::[Color][Red,Red,Blue]ghci>(read "[Red, Red, Blue]")::[Color][Red,Red,Blue]ghci>Red == RedTrueghci>Red == BlueFalseghci>Data.List.sort [Blue,Green,Blue,Red][Red,Green,Blue,Blue]ghci>Red < BlueTrue
Notice that the sort order for Color
was based on the order that
the constructors were defined, not on an alphabetical ordering.
Automatic derivation is not always possible. For instance, if you
defined a type data MyType = MyType (Int -> Bool),
the compiler will not be able to derive an instance of Show because
it doesn't know how to render a function. You will get a compilation
error in such a situation.
In this chapter, you learned about the need for typeclasses and how to use them. We talked about defining our own typeclasses and then covered some of the important typeclasses that are defined in the Haskell library. Finally, we showed how to have the Haskell compiler automatically derive instances of certain typeclasses for your types.
[7] We provided a default implementation of both functions, which gives implementers of instances choice: they can pick which one they implement. We could have provided a default for only one function, which would have forced users to implement the other every time. As it is, users can implement one or both, as they see fit.
[8] As you will see in the section called “Lazy I/O”, Haskell doesn't actually read the entire file at this point. But for the purposes of this example, we can ignore that distinction.
[9] While these are defined as regular Haskell typeclasses without any special magic, automatic derivation is a special feature of the compiler only supported with these particular typeclasses.