Important classes
Eq
- For equality and inequality.
- Note that equality in Haskell is structural equality. There is no “object identity”, and no pointer equality.
- Supported by most datatypes, such as numbers, characters, tuples, lists,
Maybe
,Either
, ... - Not supported for function types.
Ord
For comparisons between values of the same type.
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
max :: a -> a -> a
min :: a -> a -> a
Several default definitions – you’d typically define just compare
or (<=)
.
data Ordering = LT | EQ | GT
Superclasses
class Eq a => Ord a where
...
The condition indicates that Eq is a superclass of Ord :
- You cannot give an instance for Ord without first providing an instance to Eq.
- Conversely, a constraint
Ord a => ...
on a function impliesEq a
. In other words,(Ord a, Eq a) => ...
is equivalent toOrd a => ...
.
Overloading vs. parameterization
Consider:
sort :: Ord a => [a] -> [a]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
Both functions are rather similar:
- the first takes the comparison function to use from the instance declaration for the element type of the list;
- the second is passed an explicit comparison function.
Using an overloaded function is a bit more convenient, but using sortBy
is a bit more flexible.
Interestingly, GHC implements overloaded functions by passing type class “dictionaries” as additional arguments.
Excursion: performance impact of overloading
- You pay no price whatsoever for parametric polymorphism.
- Overloaded functions get extra arguments at runtime. There is a slight performance penalty for that.
- Only overloaded functions get extra arguments – remember that there is no general run-time type information!
- It is possible to instruct GHC to generate specialized versions for overloaded functions at particular types, thereby eliminating the run-time overhead.
- GHC also has a relatively aggressive inliner. Inlining overloaded functions can also remove the overhead, much like specialization.
Show
class Show a where
show :: a -> String
showsPrec :: Int -> a -> ShowS
showList :: [a] -> ShowS
The most important method is show
:
- used to produce a human-readable
String
-representation of a value; - it is sufficient to define
show
in new instances, as the others have default definitions; - the other two functions can be used to more efficiently and
beautifully implement
show
internally (for example, remove unnecessary parentheses); - also used by GHCi to print result values of evaluated terms;
- once again, function types are not an instance.
Read
class Read a where
readsPrec :: Int -> ReadS a
readList :: ReadS [a]
Most often, the derived function read is used:
read :: Read a => String -> a
Tries to interpret a given String
(such as produced by show
) as a value of a type.
How the value is interpreted is statically determined by the context:
read "1" + 2 -- used as a number, parsed as a number
not (read "False") -- used as a Bool , parsed as a Bool
Unresolved overloading
The following function produces an error (not in GHCi, but if placed in a file):
strange x = show (read x)
The error will say something about an “ambiguous type variable” and
mention constraints for Read and Show .
Can you imagine what the problem is?
The x
is a String
which is then parsed into something by read
.
But what type should it be parsed at? The context does not tell,
because the result is passed to Show
, which is also overloaded.
Manually resolving overloading
This works:
strange :: String -> String
strange x = show (read x :: Bool)
Or this:
strange :: String -> String
strange x = show (read x :: Int)
But note that the choice of intermediate type does make a difference!
In general, if several overloaded functions are combined such that the resulting type does not mention any overloaded variables anymore, you have to specify the intermediate types manually to help the type checker resolve the overloading.
deriving
For a limited number of type classes (but in particular Eq , Ord , Show , Read ), the Haskell compiler has a built-in algorithm to derive an instance for nearly any datatype.
deriving
For a limited number of type classes (but in particular Eq
, Ord
,
Show
, Read
), the Haskell compiler has a built-in algorithm to derive
an instance for nearly any datatype.
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving (Eq, Ord, Show, Read)
Defines the Tree
datatype of binary trees together with suitable
instances:
- equality is always deep and structural;
- ordering depends on the order of constructors;
- Show and Read assume the natural human-readable Haskell string representation.