(Algebraic) Data Types
Type-safe abstractions
Defining multi-field records
data StudentT
=
Student
String Int
type name beingdefined keyword
constructor (or wrapper) definition
field types / values in this wrapper
s1, s2 :: StudentT s1 = Student "Joe" 20 s2 = Student "Mary" 21
getName:: StudentT -> String getName (Student nm _) = nm
The type name can (only) be used in our type signatures. It always begins with capital letter.
The constructors(also capitalized) are used in
Patterns, for choosing the function leg / unwrapping the fields,
Expressions, for creating wrapped values of the given type.
Why is it called an algebraic data type?
An algebraprovides a way to combine simple elements (e.g. identifiers, constants) into more complex expressions, using operators.
An algebraicdata type provides mechanisms for assembling simple primitive types (e.g. Int, Char) into more complex multi-field or variant types.
Multi-case types
(Pascal variants, C calls them union types)
data ShapeT= Circle Double
| Triangle Double Double Double
| Rectangle Double Double area :: ShapeT -> Double area (Circle r) = pi * r^2 area (Rectangle h w) = h * w area (Triangle a b c) = b * th / 2.0
where th = ???
The type QualityStreetSweetis a union of sixteen variants. Variants are identified by unique constructors / wrappers, which encapsulate their data.
Was this possibly the inspiration for Haskell’s type system?
Multi-case types – another example
data ColourT =Red
| Green
| RGB Int Int Int
| GreyScale Int
| HSV Int Int Int toRGB:: ColourT -> ColourT toRGB Red = RGB 255 0 0 toRGB Green = RGB 0 255 0
toRGB (GreyScale v) = RGB v v v toRGB (HSV h s v) = … toRGB c = c -- catchall
A type that permits multiple ways to represent a colour.
A function to switch between representations.
enums fit nicely into this …
data DayT =
Sun| Mon| Tue| Wed| Thu| Fri| Sat data Bool = False| True
enums are just degenerate multi-case variant types without any "field data" – empty wrappers that don’t contain any chocolates.
That is why Falseand Wedare sometimes called constructorsrather than constants.
Are types like DayT and Bool members of classes Eq, Ord, Show , and Enum?
Not unless we provide instancedefinitions, and write all the functions that the class demands.
The prelude does this for Bool.
Main> [Sun .. Sat]
ERROR - Cannot infer instance
*** Instance : Enum DayT
*** Expression : enumFromTo Sun Sat Main> Tue < Fri
ERROR - Cannot infer instance
*** Instance : Ord DayT
*** Expression : Tue < Fri
The hard way out:
Implement the interfaces …
instance Enum DayT where toEnum 0 = Sun toEnum 1 = Mon toEnum 2 = Tue toEnum 3 = Wed toEnum 4 = Thu toEnum 5 = Fri toEnum 6 = Sat fromEnum Sun = 0 fromEnum Mon = 1
…
fromEnum Sat = 6
The hard way out:
Implement the interfaces …
instance Eq DayT where
(==) x y = (fromEnum x) == (fromEnum y) instance Ord DayT where
(<) x y = (fromEnum x) < (fromEnum y)
Haskell to the rescue …
As part of our data declaration, we can tell the compiler to derive what it thinks are the
"obvious" member functions needed for Class instances.
data DayT =
Sun| Mon| Tue| Wed| Thu| Fri| Sat deriving (Eq, Ord, Show, Enum)
Main> Tue < Fri True
Main> [Sun .. Sat]
[Sun, Mon, Tue, Wed, Thu, Fri, Sat]
Now it works …
The Hugs interpreter has a setting (u+), (default on), to automatically use show(from class Show) to display results. So it is a Good Idea to either derive or implement Show.
Abstract Data Types (ADTs)
data IntSetT = IntSet [Int]
union, intersection ::
IntSetT -> IntSetT -> IntSetT
We can exposethe type name and function signatures from a Haskell module, but we can hidethe implementation and representation details (including the wrapper names) from clients. i.e. we can build secure ADTs.Hide this
union, intersection :: Ord a =>
SetT a -> SetT a -> SetT a Next, we add polymorphism…
Check yourself: can you explain the different roles of SetTand Set?
data Ord a => SetT a = Set [a]
We can keep the constructor(s) hidden inside the module …
t1 = list2Set ["abby", "joe", "bill"]
t2 = list2Set ["bill", "mary"]
t3 = intersection t1 t2 union, intersection::
Ord a => SetT a -> SetT a -> SetT a list2Set :: (Ord a) => [a] -> SetT a
This is an ADT – the client has no access to the internal representation. Usage:
Within the implementation, we use the wrappers / constructor(s) in our function bodies and patterns.
union:: Ord a => SetT a -> SetT a -> SetT a union (Set xs) (Set ys) =
Set (rawUnion xs ys) where
rawUnion::(Ord a)=>[a]->[a]->[a]
rawUnion … … = …
In the first line the bluepatterns "unwrap" the
encapsulated raw data, we operate locally on the lists, and the red Setconstructor re-wraps the result.
Exercise: How would you create a data structure to represent a binary tree?
If we can't do this yet, what do we still need to add to Haskell's type system?
At last, we get to define our own recursive data types ☺
In English, how would you describe a binary tree that holds a value at each node?
"A binary tree is either empty, or it is a node with a value, and a left subtree and a right subtree. Each subtree is a tree."
data TreeT a
= Empty
| Node
(TreeT a)a (TreeT a) Beautiful, clever Haskell lets us express our intent exactly, with polymorphism too:
deriving Show
t1 = Node
(Node Empty "Haskell" Empty)
"is"
(Node(Node Empty "really" Empty)
"quite"
(Node Empty "amazing" Empty)) is
☺Haskell ☺ quite
☺really ☺ ☺amazing ☺ t1
is
☺Haskell ☺ quite
☺really ☺ ☺amazing ☺ t1
mkLeaf x = Node Emptyx Empty t1 = Node (mkLeaf "Haskell")
"is"
(Node(mkLeaf "really")
"quite"
(mkLeaf "amazing")) t2 = Node (mkLeaf 1) 2 (mkLeaf 3)
2
☺1 ☺ ☺3 ☺ t2
Tree Exercises
Write a function to sum all the elements in a tree of Int.
Generalize this to work for any kind of number.
sumT :: (Num a) => TreeT a -> a
sumT
Empty= 0 sumT (Node l n r) =
sumT l + n + sumT r
Write a function to produce the mirror- image of a tree.
is
☺Haskell ☺ quite
☺really ☺ ☺amazing ☺
is
☺Haskell ☺ quite
☺really ☺
☺amazing ☺
A binary tree in which every node has exactly two or zero children is a strict binary tree, sometimes called a fulltree. Write a predicate to determine whether a tree is strict.
is Haskell quite
really amazing
2
1
3
Write flatten, which takes a tree and produces a list of values from the tree, (in-order traversal, i.e. LNR order).
1 2 5
4 3
[3, 2, 4, 1, 5]
flatten :: TreeT a -> [a]
Notice how the "form of the program"
closely mirrors the "form of the data".
flatten Empty = [ ] flatten (Node l v r) =
flatten l ++ [v] ++ flatten r
Write a function that doubles every element in a binary tree of numbers.
Write a function which returns the height of a binary tree.
Write a function which counts the number of non-leaf nodes in a binary tree.
A binary search tree(BST) has special ordering properties – what are they?
Write a function which inserts a new element into a BST.
Summary
The type system is type-safe.
It supports polymorphism.
Allows one to express enumerations, variants, and recursive typeselegantly.
Plays together particularly well with Haskell's pattern matchingand multi-leg definitions.