Haskell 2, Spring 20
Propose: To learn the functional style of programming.
Records, lists, user types, function types and local definitions will be
discussed.
- Introductory examples: SimpleExamples.lhs
- Examples: Notes.lhs
- Haskell/Type
basics
Haskell -- "=" and "::"
- Functional programming refers to writing a program as a set of
functions
- "=" means defined as
> sq x = x^2
> cube x = x *(sq x)
"::" means has type
- Haskell can infer the
type of the function:
...> :t sq
sq :: Num a => a -> a
- Num is a
Class. Classes in Haskell are set of types. Int
and Float are examples of types which are in the Class
Num.
- Users can use "::" to specify the type of a definition.
- To specify py as a Float rather than the Double type determined by
HUGS, insert the type in the script.
> py :: Float
> py = 3.14159
- Constraining the function "sq"
- > sq :: Int -> Int
- > sq x = x^2
See Haskell Lab 1.
Type system
- Haskell's type system has unusual features and is quite rich
- Haskell is statically typed
Types and values
- Every value has a type
- Functions are values
- Values are first class, that is,
- they can be applied as arguments to functions
- returned
Predefined types
Basic types
- Some basic atomic values and types:
- Predefined data types
Int, Bool, Char, Float, ...
also Double, Integer
read "::" as "has type"
5 :: Int
5.0 :: Float
Float & Double need a decimal point or an exponent: 5.0
'a' :: Char
Char: the character type, 'a', 'P', '<',
etc. ASCII codes can be used: '\65'. Version 1.4 has
started to use Unicode.
"aba" :: String
Later we use keyword type
to define synonyms.
- A new type is not defined but a new name is given to an existing
type. (Like typedef in C)
Structured types Lists and Tuples
- Structured values
- List Processing is what gave LISP its name and
strength: Lisp allows structures of nested lists and each element of
the list or nested list can have a different type
- Non-homogeneous lists have proved to be very flexible structures,
supporting artificial intelligence and other sophisticated
applications
Lists
- Haskell lists are NOT like Lisp lists, they have to be homogeneous
- They are dynamic (can change length during execution)
- Lists are homogeneous: all elements in the list are the same type
- List's type is expressed by enclosing its elements' type in brackets
[True,False,False]::[Bool]
[1,2,3] :: [Int]
(actually ...>:t [1,2,3] gives [1,2,3]::Num a => [a])
[(+),div,(*)] :: Integral a => [a -> a -> a]
[ ] has two roles
- value constructor, as in [ ]. [4], [m+n, m-n]
- type constructor, as in [Int], [Char], [Bool], [a]
Strings
- The literal syntax of string is a list of chars. Prelude
defines string
type String = [Char]
- The string literal "aba" is shorthand for ['a','b','a']
- We will visit lists many times.
- By the way, this cursor indicates Haskell is garbage
collecting
List operations
- For now, the important operations:
1:[2,3,4] = [1,2,3,4]
4:[] = [4]
head [1,2,3,4] = 1
tail [1,2,3,4] = [2,3,4]
init [1,2,3,4] = [1,2,3]
last [1,2,3,4] = 4
What is the type of
:t (:)
:t :
will produce an error because ":" is an infix operator
To convert it to a prefix operator use "( )"
:t head
?
:t init
Concatenation (appending) two lists
[1,2,3] ++ [10,11,12] = [1,2,3,10,11,12]
Definition:
(++) :: [a]->[a]->[a]
[] ++ ys = ys
(x:xs) ++ ys = x:(xs ++ ys)
What if we recurs on ys instead of xs?
Notice that (++) is associative operation.
Tuples
- There are non-homogeneous linear structures like the 'struct' of C or
the 'record' of Pascal and Ada:
- They are called 'tuples'and is built using parentheses and commas;
its type is the tuple of its components types.
('x', 99) :: (Char, Int)
type Person = (String,Int)
Compare
struct person {
char * name;
int i;
}
The parenthesis/comma syntax appears in two roles:
- value constructor, as in ("Mary Jane",21)
- type constructor, as in (String,Int)
type Zip_code = (String, Int)
First letter must be capital letter, you cannot use "zip_code" or
"Zip-code"
vestal :: Zip_code
vestal = ("NY", 13850)
fst vestal returns NY
snd vestal returns 13850
type just creates a
synonym and not another data type
User-defined types
- User Defined Types: type constructors
- union type:
data Color = Red | Blue | Black
Red, Blue and Black are data constructors of the type
Color
Example of a predefined type
data Bool = True | False
Product types
- Product type is an alternative to tuples
> type Name = String > type Age = Int > data People = Person Name Age > x = Person "John" 25
|
> showPerson :: People -> String > showPerson (Person n a) = n ++ " aged " ++ show a
|
- Keyword type creates a synonym
- Keyword data is a constructor for a NEW data type.
Polymorphic data types
Point is a polymorphic data type: it
works for any type 'a'
-
- Point a is the data type
- Pt is a constructor
Pt 2.0 3.0 :: Point Float
Pt 'a' 'b' :: Point Char
Pt True False :: Point Bool
A function to extract the individual values would be:
> firstCoord :: Point a -> a > firstCoord (Pt m n) = m
|
- Point is a first order data type
- Point by itself is a type
- There are no values in Haskell that have this type.
Tree
is a recursive polymorphic data type:
> data Tree a = Leaf a |
Branch (Tree a) (Tree a) |
- recursive because Tree is defined in terms of itself
- polymorphic because it works for any type 'a'
- Tree is the data
type
- Leaf
and Branch are constructors
- A function to compute the size (i.e. the number of leaves)
>
treeSize :: Tree a -> Integer
> treeSize (Leaf x) = 1
> treeSize (Branch t1 t2) = treeSize t1 + treeSize t2 |
- Example of a Tree Int
> tree = Branch (Leaf 3) (Branch (Leaf 6) (Leaf 9))
Function types
- Functions are values and have a type
\x -> x+1,
This is a lambda expression with type:
Num a => a -> a
Notice functions can be anonymous (i.e. no names)
Functions are normally defined by an equation or series of equations
inc n = n + 1
(alternatively: inc = \n -> n + 1)
The type signature declaration used to declare the explicit
type
> inc :: Int -> Int
Type inference
- If the type is not explicitly declare then the type system can infer
the correct most general type.
inc :: Num a => a -> a
- In the tutorial, Hudak et al. use e1 => e2
to indicate that e1 reduces to e2.
My notes use the symbol "~>"
inc ( inc 3 ) ~> 5
Notice we need the "()" , inc inc 3 will give an error
Function composition
- What is the associativity of functions: f g x ??
- A type of "polymorphism" that has nothing to do with data structures
is the function composition.
- Haskell the infix operator (.) as follows:
(.) :: (b -> c) -> (a -> b) -> a -> c
(f.g)x = f(g x)
- Composition is a way to GLUE functions together!
...> (inc . inc) 3
5
Or in the script (like math f o g) :
inc2 = inc . inc
- What is the type of inc and inc2?
Currying
- In the development of functional notations as a model of computation,
it has been important to concentrate on functions of one variable
- Curry did considerable work in the area. The idea is often used today
in mathematics.
Currying a function
f : X x Y -> Z
can be thought of as a function
fc:X -> (Y -> Z)
where (Y -> Z) is the set of functions from Y
to Z
It is not a difficult idea
- (+) 3 5 ~> (3 +) 5 ~> 8
- (3 +) is a function that adds 3 to any value
- vs + (3,5) ~> 8
- must have both values
Function application in a curried language
- Example
- Functions are left associative
- The follow will cause an error!
(+) 3 (*) 2 4
- NEED "( )"
(+) 3 ((*) 3 4)
Regular logs from general logs
logBase::Float->Float->Float
The function that returns the logarithm of a number using base 10:
...>:t logBase 10
logBase 10 :: Float -> Float
Apply the function logBase 10 to 100
...> logBase 10 100
2
Using where clause
- Local definitions (scope) via where
- where can be used to
introduce a definition on the command line
Notes> f 3 4 where f x y = (x + y)/2.0
3.5
- Command line where produces
a
parse error in GHCi
- where can be used for more readability in your scripts
quadroot :: Float -> Float -> Float -> [Float]
quadroot a b c
| delta < 0 = error
"complex roots"
| delta == 0 = [term1]
| delta > 0 =
[term1+term2, term1-term2]
where
delta = b*b -
4*a*c
radix = sqrt
delta
term1 =
-b/(2.0*a)
term2 =
radix/(2.0*a)
- Compare with with a code from Pascal
type root_list = ^root_type;
type root_type = record
root: real;
next root_list
end;
function quadroot( a, b, c: real): root_list;
var delta, radix, term2: real;
roots, temp: root_list;
begin
roots := NIL;
delta := b*b - 4*a*c;
if delta < 0
then write(�Error: Complex Roots�);
else begin
radix := sqrt(delta);
term2 := radix/(2*a);
new(temp);
roots^.next:=temp;
temp^.root := roots^.root - term2;
roots^.root := roots^.root + term2;
temp^.next := NIL
end
end;
quadroot := roots
end;
Using let expressions
- let expressions also make it
possible for an expression to have local definitions
- Like where clause can
be part of a script or used on the command line (in GCHI)
Notes> let f x y = (x + y)/2.0 in f 3 4
3.5
In addition GHCI allows
Prelude> let f x y = (x + y)/2.0
Prelude> f 3 5
4.0
- The standard Haskell function definition form is syntactic sugar for
lambda expressions
- > twice x = x + x
> twice = (\x -> x + x)
twice ( 3 * 3)
~> let x = 3*3 in x + x
~> let x = 9 in x + x
~> 9 + 9
~> 18
- > quadrootL :: Float ->
Float -> Float -> [Float]
> quadrootL a b c =
> let delta = (b*b
- 4*a*c)
>
radix
= sqrt delta
>
term1
= -b/(2.0*a)
>
term2
= radix/(2.0*a)
> in
> if delta
> 0 then [term1+term2, term1-term2]
>
else
if delta < 0 then error "complex roots"
>
else
[term1]
- Since let is an expression
it can be used anywhere:
>
val = 4 * (let a = 9 in a + 1) + 2