Algebraic Data Types
Prerequisites: Pattern matching
The data keyword defines your own types. Haskell's data types are
called algebraic data types (ADTs), because they're built from two
operations: "or" (sum types) and "and" (product types).
Sum types: one of several choices
data Color = Red | Green | Blue
deriving (Show, Eq)
describe :: Color -> String
describe Red = "warm"
describe Green = "natural"
describe Blue = "cool"
main :: IO ()
main = do
print Red
putStrLn (describe Blue)
print (Red == Green)
print (map describe [Red, Green, Blue])
Red
cool
False
["warm","natural","cool"]
Red, Green, and Blue are constructors — the only possible values
of type Color. deriving (Show, Eq) asks GHC to generate code so the
values can be printed and compared. Bool itself is defined in the
standard library as data Bool = False | True.
Constructors with fields
Each constructor can carry data:
data Shape
= Circle Double
| Rectangle Double Double
| Triangle Double Double Double
deriving (Show)
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) = sqrt (s * (s - a) * (s - b) * (s - c))
where
s = (a + b + c) / 2
main :: IO ()
main = do
let shapes = [Circle 1, Rectangle 3 4, Triangle 3 4 5]
mapM_ print shapes
print (map area shapes)
print (sum (map area shapes))
Circle 1.0
Rectangle 3.0 4.0
Triangle 3.0 4.0 5.0
[3.141592653589793,12.0,6.0]
21.141592653589793
- A constructor is also a function:
Rectangle :: Double -> Double -> Shape. Somap Circle [1, 2]builds a list of circles. - Pattern matching pulls the fields back out.
- If you add a new constructor and forget to handle it,
-Wallwarns you in every function that pattern matches onShape.
Recursive types
A type can refer to itself. Here is a binary search tree:
data Tree a = Leaf | Node (Tree a) a (Tree a)
deriving (Show)
insert :: Ord a => a -> Tree a -> Tree a
insert x Leaf = Node Leaf x Leaf
insert x t@(Node left v right)
| x < v = Node (insert x left) v right
| x > v = Node left v (insert x right)
| otherwise = t
toList :: Tree a -> [a]
toList Leaf = []
toList (Node left v right) = toList left ++ [v] ++ toList right
depth :: Tree a -> Int
depth Leaf = 0
depth (Node l _ r) = 1 + max (depth l) (depth r)
main :: IO ()
main = do
let tree = foldr insert Leaf [5, 2, 8, 1, 9, 3 :: Int]
print (toList tree)
print (depth tree)
print (insert 'b' (insert 'a' Leaf))
[1,2,3,5,8,9]
4
Node Leaf 'a' (Node Leaf 'b' Leaf)
Tree a is a parameterized type: a can be any type, so the same
code builds trees of numbers, characters, or strings. Ord a => says
the elements must be orderable, because insert uses <.
type and newtype
type Name = String
newtype UserId = UserId Int
deriving (Show, Eq)
greet :: Name -> String
greet n = "Hi, " ++ n
lookupUser :: UserId -> String
lookupUser (UserId 1) = "admin"
lookupUser (UserId _) = "guest"
main :: IO ()
main = do
putStrLn (greet "Ada")
putStrLn (lookupUser (UserId 1))
print (UserId 7)
Hi, Ada
admin
UserId 7
typecreates an alias —NameandStringare interchangeable.newtypecreates a genuinely new type with exactly one constructor and one field. It costs nothing at runtime, but the compiler won't let you pass a plainInt(or anOrderId) where aUserIdis expected.
Practice
Define data Suit = Hearts | Diamonds | Clubs | Spades and a function
isRed :: Suit -> Bool, then print the red suits from a list of all four.
Show solution
data Suit = Hearts | Diamonds | Clubs | Spades
deriving (Show, Eq, Enum, Bounded)
isRed :: Suit -> Bool
isRed Hearts = True
isRed Diamonds = True
isRed _ = False
main :: IO ()
main = print (filter isRed [minBound .. maxBound])
[Hearts,Diamonds]
Deriving Enum and Bounded lets [minBound .. maxBound] list every
value of the type.