Type Classes
Prerequisites: Algebraic data types
A type class describes a set of operations that many types support. It's similar to an interface in Java or a trait in Rust, but types can join a class after they're defined, and classes can have default methods.
You've already seen constraints like Eq a => and Num a =>. The most
common standard classes:
| Class | Operations | Meaning |
|---|---|---|
Eq |
==, /= |
Can be compared for equality |
Ord |
<, compare, max, ... |
Can be ordered (requires Eq) |
Show |
show |
Can be turned into a String |
Read |
read |
Can be parsed from a String |
Enum |
succ, [a ..] |
Can be enumerated |
Bounded |
minBound, maxBound |
Has a smallest and largest value |
Num |
+, *, negate, ... |
Numeric |
Deriving instances
For most classes, GHC can write the code for you:
data Priority = Low | Medium | High
deriving (Show, Read, Eq, Ord, Enum, Bounded)
main :: IO ()
main = do
print (Low < High, maximum [Medium, Low, High])
print [minBound .. maxBound :: Priority]
print (succ Low)
print (read "High" :: Priority)
(True,High)
[Low,Medium,High]
Medium
High
Derived Ord orders constructors in the order they are declared.
Writing instances by hand
data Temperature = Celsius Double | Fahrenheit Double
toCelsius :: Temperature -> Double
toCelsius (Celsius c) = c
toCelsius (Fahrenheit f) = (f - 32) * 5 / 9
instance Eq Temperature where
a == b = abs (toCelsius a - toCelsius b) < 0.001
instance Show Temperature where
show (Celsius c) = show c ++ " C"
show (Fahrenheit f) = show f ++ " F"
instance Ord Temperature where
compare a b = compare (toCelsius a) (toCelsius b)
main :: IO ()
main = do
print (Celsius 100 == Fahrenheit 212)
print [Celsius 25, Fahrenheit 50]
print (maximum [Celsius 30, Fahrenheit 100, Celsius 20])
True
[25.0 C,50.0 F]
100.0 F
instance Eq Temperature wheresupplies the class methods for the type.Eqonly needs==;/=has a default definition.- Hand-written instances let equality mean what makes sense for your domain — here, two temperatures are equal if they're physically equal.
Constraints in your own functions
describeAll :: Show a => [a] -> String
describeAll xs = unwords (map show xs)
largest :: Ord a => [a] -> Maybe a
largest [] = Nothing
largest xs = Just (maximum xs)
countEqual :: Eq a => a -> [a] -> Int
countEqual x = length . filter (== x)
main :: IO ()
main = do
putStrLn (describeAll [1, 2, 3])
putStrLn (describeAll [True, False])
print (largest "haskell", largest ([] :: [Int]))
print (countEqual 'a' "banana")
1 2 3
True False
(Just 's',Nothing)
3
Write the most general constraint that works: countEqual only needs
Eq, so it works for any type with equality.
Common mistakes
- Forgetting
deriving (Show)and then trying toprinta value: "No instance for (Show Color)". Add the deriving clause. - Deriving
Eqon a type whose "sameness" isn't structural (likeTemperatureabove) — write the instance by hand instead.