06 Inductive Data Types
06 Inductive Data Types
Lars-Henrik Eriksson
Functional Programming 1
Today
Type Bindings
For instance,
The new name becomes synonymous with the type expression. Both can
be used interchangeably.
For instance,
For instance,
All type variables that appear in the type expression (i.e., on the right)
must be mentioned as a parameter (i.e., on the left).
New Types
Enumeration Types
For instance,
> North;
val it = North: direction
The types bool and unit each have a finite number of values. They could
(in principle) be declared as enumeration types as follows:
(* compare (a, b)
TYPE: int * int -> order PRE: true POST: ... EX: ...
*)
fun compare (a, b) =
if a < b then
LESS
else if a = b then
EQUAL
else (* a > b *)
GREATER
order and Int.compare are already declared in the SML Basis Library.
Lars-Henrik Eriksson (UU) Inductive Data Types 12 / 42
New Types Constructors
Constructors
In a datatype declaration
datatype identifier = C1 | ... | Cn
C1, . . . , Cn are called constructors.
Constructor Patterns
(* opposite d
TYPE: direction -> direction
PRE: true
POST: the direction opposite d
EXAMPLES: opposite North = South
*)
fun opposite North = South
| opposite South = North
| opposite East = West
| opposite West = East
(* sign n
TYPE: int -> int
PRE: true
POST: ~1 if n<0, 0 if n=0, 1 if n>0
EXAMPLES: sign 42 = 1
*)
fun sign n =
case Int.compare (n, 0) of
LESS => ~1
| EQUAL => 0
| GREATER => 1
For instance,
(* qadd (x, y)
TYPE: rational * rational -> rational
PRE: x and y are rational numbers
(with denominator <> 0)
POST: the sum of x and y
EXAMPLES: qadd (Rat (1,2), Rat (1,3)) = Rat (5,6)
*)
fun qadd (Rat (a,b), Rat (c,d)) =
Rat (a*d + b*c, b*d)
Example: ’a option
For instance,
> NONE;
val it = NONE: ’a option
> SOME;
val it = fn: ’a -> ’a option
> SOME 42;
val it = SOME 42: int option
> SOME "foo";
val it = SOME "foo": string option
> SOME [];
val it = SOME []: ’a list option
All type variables that appear in an argument type (i.e., on the right) must
be mentioned as a parameter (i.e., on the left).
The argument types of constructors may refer to (instances of) the data
type that is being declared.
Example: ’a list
The type ’a list of lists (with elements from ’a) is an inductive type.
1 Base case:
[] : ’a list
2 Inductive step:
If x : ’a and xs : ’a list, then x :: xs : ’a list.
For instance, 1 + 2, 3 · 4 + 5, . . .
Let us define arithmetic expressions (that involve addition and
multiplication over integers) inductively:
1 Base case:
Each integer is an arithmetic expression (aexp).
2 Inductive step:
If e1 and e2 are aexps, then e1 + e2 is an aexp.
If e1 and e2 are aexps, then e1 · e2 is an aexp.
For instance, 1 + 2, 3 · 4 + 5, . . .
Let us define arithmetic expressions (that involve addition and
multiplication over integers) inductively:
1 Base case:
Each integer is an arithmetic expression (aexp).
2 Inductive step:
If e1 and e2 are aexps, then e1 + e2 is an aexp.
If e1 and e2 are aexps, then e1 · e2 is an aexp.
For instance,
For instance,
Trees
A tree where each inner node has exactly two children is called a full
binary tree.
Full binary trees (with labels of type ’a) can be defined inductively:
1 Base case:
Each value of type ’a is a full binary tree, namely a leaf.
2 Inductive step:
If x is of type ’a and t1 and t2 are full binary trees, then
Node (t1 , x, t2 ) is a full binary tree.
In SML:
Full binary trees (with labels of type ’a) can be defined inductively:
1 Base case:
Each value of type ’a is a full binary tree, namely a leaf.
2 Inductive step:
If x is of type ’a and t1 and t2 are full binary trees, then
Node (t1 , x, t2 ) is a full binary tree.
In SML:
For instance,
(* root_value t
TYPE: ’a fbtree -> ’a
PRE: true
POST: the value at t’s root node
EXAMPLES: root_value (Leaf "foo") = "foo"
*)
For instance,
(* root_value t
TYPE: ’a fbtree -> ’a
PRE: true
POST: the value at t’s root node
EXAMPLES: root_value (Leaf "foo") = "foo"
*)
fun root_value (Leaf x) = x
| root_value (Node (_, x, _)) = x
(* height t
TYPE: ’a fbtree -> int
PRE: true
POST: the height of t
EXAMPLES: height (Leaf "foo") = 0
*)
(* height t
TYPE: ’a fbtree -> int
PRE: true
POST: the height of t
EXAMPLES: height (Leaf "foo") = 0
*)
(* VARIANT: size of t *)
fun height (Leaf _) = 0
| height (Node (l, _, r)) =
1 + Int.max (height l, height r)
Mirror Image
Let’s write a function that “mirrors” a full binary tree by (recursively)
exchanging left and right subtrees. For instance,
(* mirror t
TYPE: ’a fbtree -> ’a fbtree
PRE: true
POST: the mirror image of t
EXAMPLES: mirror (Node (Leaf 1, 2, Leaf 3)) =
Node (Leaf 3, 2, Leaf 1)
*)
(* mirror t
TYPE: ’a fbtree -> ’a fbtree
PRE: true
POST: the mirror image of t
EXAMPLES: mirror (Node (Leaf 1, 2, Leaf 3)) =
Node (Leaf 3, 2, Leaf 1)
*)
(* VARIANT: size of t *)
fun mirror (Leaf x) = Leaf x
| mirror (Node (l, x, r)) = Node (mirror r, x, mirror l)