Week 2

Motivating Types (I)
All the syntactic sugar to date has in principle been convertible back to original λ-calculus expressions.
So,
(λ m n s • n (m s)) (λ s z • s(s(s z ))) (λ t f • t)
looks innocent enough, and reduces to:
(λ s • (λ s z • s(s(s z ))) )
Motivating Types (II)
However, as well as “Church Numerals” there are “Church Booleans”:
True =
b λt f • t
False =
b λt f • f
If =
b λc t e • c t e
In this case we can interpret the previous example as:
β
3 + True 7→ λ s • 3
that is not either a sensible number or boolean.
Wouldn’t it be nice to have types?
(Simply) Typed λ-Calculus
We define types (Type) to be either basic-types (BType) or function types:
s, t, . . . ∈ BType
σ, τ ∈ Type ::= s
|
σ→τ
We can add type annotations to the variables in the lambda calculus:
x :τ
We can then give typing rules:
• If M : σ, then (λ x : τ • M ) : τ → σ.
• If M : σ → τ and N : σ, then (M N ) : τ .
Typed λ-calculus is different from the un-typed variety — they cannot be inter-converted.
Typed λ-Calculus: styles
In principle we should decorate all variables:
λx : σy : τ • x : σ
1
but in practice, we only annotate the binding occurrences:
λx : σy : τ • x
or give a signature for the function, and leave variables alone:
: σ→τ →σ
=
b λx y • x
const
const
Here σ → τ → σ is sugar for σ → (τ → σ)
Sugared Typed-λ-calculus
We can then add in a range of syntactic sugar for types, basic and compound:
N, Z, Q, ×, ∗
and extend the expression notation:
37, −35, 46.13, (13, 23), h1, 2, 3i
We can now write definitions like:
length
length seq
: Q∗ → N
=
b if null seq then 0 else 1 + (length(tail seq))
Look vaguely familiar?
!?!?!! Recursion ! How did that get there ? (Later)
Haskell
• Pure, lazy functional language i.e. well-sugared typed λ-calculus.
• Named for logician Haskell B. Curry
• Many multiple-platform implementations:
– compilers — GHC, yhc, nbc, . . .
– interpreters — Hugs, WinHugs.
• www.haskell.org
Haskell: Basic Types, Constants and Operators
As expected, Haskell supports all the usual basic types:
Format Type,
Values,
Example Expression.
Boolean Bool,
True, False,
not False && (True || b)
Integers Int,
0, 10, -34,
3 * (23 - 16 * i)
Operators,
not, &&, ||,
+, -, *, /,
Floating-Point Float,Double,
0.0, 1.23E+5,
3.14159 * (23E-3 - 1.6 * r)
Characters Char,
’a’,’B’,’\n’,’\xd’,
chr(ord ’A’ + ord c - ord ’a’)
+, -, *, /,
ord, chr, isAlpha,
2
Haskell: Composite Types
Assume s, t and u are pre-existing types.
Function Types s -> t,
(f . g) 3
f x = x * x, \ y -> y+3,
Tuples (s,t),(s,t,u), (1,True), (’a’,42),
(fst(True,999),snd(False,42))
Lists [t], [],[1,1,2,3,5,8],
tail ([1,2,3]++[4,5,6])
., map, foldl,
fst, snd,
:, length, head, tail, ++,
Strings String is synonym for [Char],
"" is really [],
while "abc" is syntactic sugar for [’a’,’b’,’c’].
Haskell: Scripts
A Haskell programme (or script) has the following general format:
module ModuleName where
import Import1
...
list of definitions
A definition is typically of a function, of the form:
fname arg1 . . . argn = expression defining function
Haskell: Demo
Haskell Features
We take a brief look at some important Haskell features:
• Lists
• Pattern Matching
• Type Polymorphism
• ADTs (Abstract Data Types)
• Namespaces
• Function and Operator Fixity
3
Lists (I)
Lists are a major datatype in Haskell:
Type We use the notation [t] to denote a list whose elements are of type t.
Constructing Values There are two basic ways to define a list:
• We write the empty list as []. It has type [t] for any type t.
• Given an element x of type t and a list xs of type [t], we can stick the element on the
front (“cons-ing”), written x:xs — the infix colon : is the list constructor.
Accessing Values There are two ways to get at the components of a list:
• Using functions null, head and tail
• Pattern matching on the two ways to build a list.
Lists (II)
Functions null, head and tail can be defined by pattern matching:
null
null
head
tail
[] = True
(x:xs) = False
(x:xs) = x
(x:xs) = xs
Note that head and tail are partial, being undefined for the empty-list.
The list enumeration notation [x1 , x2 , . . . , xn ] is syntactic sugar for x1 : (x2 : (. . . (xn : []) . . .))
There are a long list of functions available for list manipulation —see the Haskell “Prelude”
http://haskell.org/onlinereport/standard-prelude.html.
Type Polymorphism (I)
Consider the list length function:
length [] = 0
length (x:xs) = 1 + length xs
What type does it have?
[ Demo: Taking List Lengths ]
Type Polymorphism (II)
The function length works with lists of any type, and returns an Integer:
length :: [t] -> Int.
This works because it does not manipulate the elements in any way, but simply counts them.
Polymorphic (“many-shaped”) types are simply types with parts, represented by type variables,
that can take on any type (Haskell uses letters a, b, c,. . . for these).
E.g. function reverse :: [t] -> [t] takes a list of any type and reverses it to give a list
of the same type.
4
Abstract Data Types (I)
We can build our own datatypes by describing them as a collection of Constructors that build a
value of the type from other types.
General framework:
data MyType = C1 t11 t12 · · · t1k1
| C2 t21 t22 · · · t2k2
..
.
| Cn tn1 tn2 · · · tnkn
Here all the ki , for i ∈ 1 . . . n can be zero or more.
This is read as declaring MyType to be either a C1 built from t11 through to t1k1 , or a C2
built . . .
The names MyType can appear as one of more of the tij (Recursive types allowed).
Abstract Data Types (II)
We can define functions on these by pattern-matching:
f
:: MyType− > AnotherType
f (C1 p11 p12 · · · p1k1 ) = what to do with C1 variant
f (C2 p21 p22 · · · p2k2 ) = what to do with C2 variant
..
.
f (Cn pn1 pn2 · · · pnkn ) = what to do with Cn variant
Here pij is a pattern matching a value of type tij .
Abstract Data Types (III)
Example 1 - Days of the Week.
All the ki can be zero:
data Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun
isWeekDay :: Day -> Bool
isWeekDay Sat = False
isWeekDay Sun = False
isWeekDay _
= True
The pattern is a wildcard and matches anything.
Patterns are tried in the order listed.
Abstract Data Types (IV)
Example 2 - Binary Tree of Integers
data BinTreeInt
= LeafI Int | BranchI BinTreeInt Int BinTreeInt
btiSize (LeafI _) = 1
btiSize (BranchI left _ right)
= 1 + btiSize left + btiSize right
So the following is a value of type BinTreeInt
BranchI (LeafI 1) 2 (LeafI 3)
5
Abstract Data Types (V)
Example 2 - Binary Tree of anything
We can put a type parameter (type variable) immediately after the name of our type, which
can then be used as one of the tij .
data BinTree t
= Leaf t | Branch (BinTree t) t (BinTree t)
btSize (Leaf _) = 1
btSize (Branch left _ right) = 1 + btSize left + btSize right
So the following is a value of type BinTree Char
Branch (Leaf ’a’) ’b’ (Leaf ’c’)
Namespaces
In Haskell we can classify identifier tokens into two types:
• Those starting with lowercase alpha followed by alphanum:
a, b1, idWithCapSepWords, identifier with underscores.
These are used for names of functions and variables/values
• Those starting with uppercase alpha followed by alphanum:
A, B1, True, Leaf, Branch
These are used as names of Types and Type-/Data-Constructors
Function and Operator Fixity
In Haskell we write function/operators in two ways:
• conventional identifiers (lowercase alpha followed by alphanum:
a, b1, idWithCapSepWords, identifier with underscores.
These are prefix function names by default.
• Symbols (“funny” characters)
++, <=, :, $!$, $, =:::=.
These are infix operator names by default.
Prefix function names (of arity 2) and infix operators can be converted into each other as follows:
Normal Use Converted Use
f a b
a ‘f‘ b
a + b
(+) a b
6
Scribbles 5 : A Binary Tree
The binary tree BranchI (LeafI 1) 2 (LeafI 3) can be viewed as
BranchI
1
2=
===
==
==
==
==
==
=
Leaf
3
Leaf
Type Classes in Haskell
Haskell allows us to define the notion of type classes:
• A class defines a number of functions/values over types of that class
• A type is an instance of a class if instances of those functions and values are defined for that
type
• Type-classes support ad-hoc overloading — the use of a single symbol to represent a number
of different but related functions.
Class Example: Equality
Assume we have builtin functions defining equality for various basic types:
primitive primEqChar
primitive primEqInt
primitive primEqFloat
:: Char -> Char -> Bool
:: Int -> Int -> Bool
:: Float -> Float -> Bool
We define a class Eq (equality) which requires a symbol == to be defined:
class Eq a where
(==), (/=) :: a -> a -> Bool
-- Minimal complete definition: (==) or (/=)
x == y
= not (x/=y)
x /= y
= not (x==y)
We can now define instances of == for Char, Int and Float:
instance Eq Char
instance Eq Int
instance Eq Float
where (==)
where (==)
where (==)
= primEqChar
= primEqInt
= primEqFLoat
We can now use == to denote equality between these different types.
7
More Equality
We can define class instances which depend on others.
For example, given any type of class Eq (i.e. for which == is defined), we can define equality
over lists of that type:
instance Eq a
[]
==
(x:xs) ==
_
==
=> Eq [a]
[]
=
(y:ys) =
_
=
where
True
x==y && xs==ys
False
Given previous definitions, this now means == can be used with arguments of type [Char], [Int],
[Float].
Indeed we can nest such instances, so equality is now also defined for [[Char]], [[[Int]]],
etc.
In Haskell, == is predefined for all its builtin-types, (except for functions).
Class Example: Ordering
A class definition can depend on another — consider the ordering class Ord which defines <=, >=,
among others.
We have an ordering result type Ordering, and again some builtin comparison operators:
data Ordering = LT | EQ | GT
primitive primCmpChar
primitive primCmpInt
primitive primCmpFloat
:: Char -> Char -> Ordering
:: Int -> Int -> Ordering
:: Float -> Float -> Ordering
We define the class Ord as follows:
class (Eq a) => Ord a where
compare
:: a -> a -> Ordering
(<), (<=), (>=), (>)
:: a -> a -> Bool
max, min
:: a -> a -> a
.... details omitted ....
The complete definition allows us to define everything in terms of either compare or <.
Class Example: Show
There is a class Show concerned with converting a value into a printable string:
class Show a
showsPrec
show
showList
where
:: Int -> a -> ShowS
:: a -> String
:: [a] -> ShowS
-- Mimimal complete definition:
-show or showsPrec
showsPrec _ x s
= show x ++ s
A value needs to be of class Show in order for Hugs to show it.
8
Deriving Instances
For certain builtin classes (Eq, Ord, Show) we can ask the compiler to automatically generate
instances for our ADTs:
data BinTreeInt
= LeafI Int | BranchI BinTreeInt Int BinTreeInt
deriving (Eq,Ord,Show)
Haskell will define equality in the “obvious” way, and come up with an ordering where a Leaf is
less than a Branch.
Values of type BinTreeInt will print as we would write them in Haskell.
Class Example: Numbers
There are a range of classes defining different aspects of numbers.
The most prevalent is Num:
class (Eq a, Show a) => Num a where
(+), (-), (*)
:: a -> a -> a
negate
:: a -> a
abs, signum
:: a -> a
fromInteger
:: Integer -> a
-- Minimal complete
-All, except
x - y
= x +
negate x
= 0 -
definition:
negate or (-)
negate y
x
Mini-Exercise 1
Show that
β
7→ 4
2+2
Hints:
1. Use rightmost-innermost reduction order here.
2. You might want to pull an inner redex out, do the reduction separately, and then plug the
result back in.
β
3. Try 1 + 1 7→ 2 as a warm-up!
Due: at start of 4pm Lecture, Thursday, February 14th, 2008.
9
Mini-Solution 1
=
=
2+2
PLUS 2 2
(λ m n s z • m s (n s z )) (λ s z • s(s z )) (λ s z • s(s z ))
β
7→ (λ n s z • (λ s z • s(s z )) s (n s z )) (λ s z • s(s z ))
β
7→ λ s z • (λ s z • s(s z )) s ((λ s z • s(s z )) s z )
β
7→ λ s z • (λ s z • s(s z )) s ((λ z • s(s z )) z )
β
7→ λ s z • (λ s z • s(s z )) s (s(s z ))
β
7→ λ s z • (λ z • s(s z )) (s(s z ))
β
7→ λ s z • s(s(s(s z )))
= 4
Mini-Exercise 2
For both the BinTreeInt and BinTree datatypes, provide Eq instance declarations.
Due: at start of 4pm Lecture, Thursday, February 21st, 2008.
Scribbles 6 : Method to our Madness
• We want to model in a systematic way
• Coherent view of a system as State plus Operations
– State: all the information that characterises a system at a point in time.
– Operations: mechanisms by which we can inspect or modify the state
• We shall use a standard notational style to describe these
Scribbles 6 : Sport Example: State
Associated with our model is the notion of a type of Sport identifiers:
s ∈ Sport
from which we build Sport-sets:
S ∈ Sports
=
b
PSport
Our system state is simply a pair of sets, one for sports of interest, the other for sports in which
participation occurs:
(I , P ) ∈ State =
b Sports × Sports
10
Scribbles 6 : Sport Example: Adding Interest
We now describe an operation to add a sport of interest.
We view all state-update operations as taking a parameter as first argument, and then a
(before-)state as second argument, and returning a (after-) state result:
addI
: Sport → State → State
We define the behaviour of the operation by showing how the parameter and before state combine
to give the after state:
addI (s)(I , P ) =
b (I ∪ { s }, P )
11