Functional Programming
Radu Răzvan Slăvescu
Technical University of Cluj-Napoca
Department of Computer Science
Outline
Parameter evaluation
Call-by-value
Call-by-name / by-need
Polymorphic types
Lists
Definition
Building lists
Basic functions on lists
Association lists
Parameter evaluation
Question:
When calling a function, when are the actual parameters
evaluated: before or after they replace the formal parameters?
Example (Square)
fun sq ( z ) : int = z * z;
Example (Zero)
fun zero ( x : int ) = 0;
Call-by-value
Definition
In case of ”call-by-value”, the value of the parameter is sent.
Using call-by-value
Call-by-value is used in ML.
Call-by-value
Example
sq(sq(sq(2))) =
sq(sq(2 * 2)) =
sq(sq(4)) =
sq(4 * 4) =
sq(16) =
16 * 16 =
256
Call-by-value
Example
zero(sq(sq(sq(2)))) =
zero(sq(sq(2 * 2))) =
zero(sq(sq(4))) =
zero(sq(4 * 4)) =
zero(sq(16)) =
zero(16 * 16) =
zero(256) = 0
Call-by-value and the conditional
Remeber the conditional
if E then ET else EF:
1. E gets evaluated, then ...
2. either ET or EF is evaluated, but never both of them
Example (andalso)
A andalso B is equivalent to if A then B else false
Example
fun even n = (n mod 2 = 0);
fun p2 n = (n = 1) orelse
(even(n) andalso p2 (n div 2));
Call-by-value and the conditional
Example
p2(6) =
(6=1) orelse (even(6) andalso ...) =
even(6) andalso p2(6 div 2) =
p2(3) =
even(3) andalso p2(3 div 2) =
false
Call-by-value
Question:
When call-by-value is employed, can we write a function cond
which behaves in the same way as the conditional ?
Example
fun cond (p,x,y) = if p then x else y;
Example
fun facter n = cond (n = 0, 1, n * facter(n-1));
Conditional function and call-by-value
Example
facter(0) =
cond(true,1,0*facter(-1)) =
cond(true,1,0*cond(false,1,-1*facter(-2))) =
...
Call-by-name
Definition
The expression of the parameter is sent.
Example
zero(sq(sq(sq(2)))) = 0
Call-by-name
Example
sq(sq(sq(2))) =
sq(sq(2)) * sq(sq(2)) =
(sq(2)*sq(2)) * sq(sq(2)) =
((2*2)*sq(2)) * sq(sq(2)) =
(4*sq(2)) * sq(sq(2)) =
(4*(2*2)) * (sq(*sq(2)) =
...
Call-by-need
Definition
Call-by-need (aka ”lazy evaluation”) is similar to call-by-name,
but evaluates an expression only once, when necessary.
Use of call-by-need
Haskell uses call-by-need.
Mechanism
We write [x=E] in order to say that all occurences of x in the
function’s body have access to E.
Call-by-need
Example
sq(sq(sq(2))) =
z*z [z=sq(sq(2))] =
z*z [z=y*y][y=sq(2)] =
z*z [z=y*y][y=2*2] =
z*z [z=4*4] =
16 * 16 =
256
Call-by-need
Example
f n = (2 * n , g n)
g = a hard-to-compute function
h (x,y) = x
h(f 10) = 20
Call-by-need - a not-so-cool example
Example (Sometimes, we can’t escape our fate)
sumacc a [] = a
sumacc a (x:xs) = sumacc (a+x) xs
sumacc 0 [1,2,3] =
sumacc (0+1) [2,3] =
sumacc ((0+1)+2) [3] =
sumacc (((0+1)+2)+3) []=
((0+1)+2)+3 =
(1+2)+3 = ...
Call-by-need and strict application
Note
In this example, the whole accumulator’s expression is built
before being evaluated. This is an issue for a 10M element list.
Forcing argument evaluation
$! forces strict (call-by-value style) evaluation of the argument
Example (Summing up numbers)
sumacc a [] = a
sumacc a (x:xs) = (sumacc $! (a+x)) xs
Call-by-need - strict application
Example (Summing up numbers)
sumacc a [] = a
sumacc a (x:xs) = (sumacc $! (a+x)) xs
sumacc 0 [1,2,3] =
sumacc $! (0+1) [2,3] =
sumacc $! 1 [2,3] =
sumacc 1 [2,3] =
sumacc $! (1+2) [3] =
sumacc $! 3 [3] =
sumacc 3 [3] = ...
Call-by-need - strict application
n time spent (ms.) digits of n!
5,000 31 16,327
50,000 1,575 213,238
500,000 204,696 2,632,343
1,000,000 840,458 5,565,710
2,000,000 3,500,371 11,733,841
3,000,000 7,757,876 18,128,485
5,000,000 21,295,103 31,323,383
Table: Computing n! for different values of n
Machine:
(processor, memory etc. should be given here)
Call by need - strict application
Lazy vs. strict:
50,000! no optimization optimization (-O3)
lazy 2,728 1,083
strict ($!) 1,089 1,082
Table: Time (ms.) spent to compute 50,000!
Machine:
(processor, memory etc. should be given here)
Polymorphism
Definition
A type denotes a set of values and a set of operations on them.
Strong typed versus Weak typed
Strong typing provides safety; weak typing provides flexibility.
Polymorphic types try to get the best from both worlds.
Definition
An object is polymorphic if it could be seen as having more
than one type.
Polymorphism
Definition
Type template (type schema) = a set of type variables which
can get instantiated in order to create specific types.
Definition
Polymorphic function = a function based on a type template.
Example
length :: [a] -> Int
Example
val it = fn : ’a list -> int
Polymorphism
Definition
Substitution instance = a specifc instantiation of type variables
Example (Type variable a gets instantiated to Int)
Prelude> length [1,2,3,4]
4
Example (Type variable a gets instantiated to Char )
Prelude> length [’a’, ’b’, ’c’]
3
Type inference
Definition
An expression is well typed if the type checker is able to find
consistent substitutions for all its type variables.
Inference rule for type inference
f :: A −> B e :: A
f e :: B
Type inference example
Example
fun facti(n, p) = if n=0 then p
else facti (n-1, n*p);
Type inference
1 is of type int
→ n-1 is of type int
→ n is of type int
→ n*p is integer multiplication
→ p is of type int
→ facti is of type int ∗ int− > int
Overloaded functions
Definition
Overloaded function = a polymorphic function whose type
contains one or more constraints.
Example (constraint: a must be of type number)
Prelude> :type sum
sum :: Num a => [a] -> a
Type classes in Haskell and their supported methods
I Eq (equality types): their values can be compared using ==
and /= (e.g. Int, Char, lists with elements in Eq)
I Ord (ordered types): those in Eq which can be ordered
using <, <=, >, >=, min, max
I Show (showable types): those whose values can be
converted to strings (and displayed) by using show
I Read (readable types): those whose values can be
converted from strings by using read
Type classes in Haskell and their supported methods
I Num (numeric types): numbers, on which we can apply
+, -, *, negate, abs, signum
I Integral: types which are instances of Num, on which we
can apply div, mod (e.g. Int, Integer)
I Fractional: types which are instances of Num, on which
we can apply /, recip (e.g. Float)
Polymorphic equality
Equality types
Equality is not available for every object in FP.
Definition
An equality type is a type endowed with an equality predicate. It
is represented by equality type variables, denoted α= , β = , ...
Example (Haskell: Eq a => )
Prelude> :type (==)
(==) :: Eq a => a -> a -> Bool
Example (ML: ”a)
> op =;
val it = fn : ’’a * ’’a -> bool
Polymorphic equality
Example
fun memb (x, []) = false
| memb (x,(y::ys)) = (x=y) orelse memb (x,ys);
Equality types
This function could be called only on lists whose elemets are of
equality types, e.g. real, string * int etc.
Projection functions
Definition
Projection function = returns one component of a tuple.
Example
> fun firstt (x,y) = x;
val firstt = fn : ’a * ’b -> ’a
Example
> fun secondt (x,y) = y;
val secondt = fn : ’a * ’b -> ’b
Projection functions
Example
> firstt ("one", 2);
val it = "one" : string
Example
> secondt ("one", 2);
val it = 2 : int
Projection functions
Example
> fun firstfirstt x = firstt (firstt x);
val firstfirstt = fn : (’a * ’b) * ’c -> ’a
Note
One function could have different types in the same expression.
Innermost firstt:
(’a * ’b) * ’c -> ’a * ’b
Outermost firstt:
’a * ’b -> ’a
Some more type inference examples
Example (2 arguments, parentheses)
type Vector = (Int, Int)
gcd4 :: Vector -> Int
gcd4 (m,n) = if m == 0 then n
else gcd4 (n ‘mod‘ m, m)
gcd4 :: Vector -> Int
Example (2 arguments, parentheses)
type Vector = (Int, Int)
gcd3 (m,n) = if m == 0 then n
else gcd3 (n ‘mod‘ m, m)
gcd3 :: Integral a => (a,a) -> a
Some more type inference examples
Example (2 arguments, parentheses)
gcd2 (m,n) = if m == 0 then n
else gcd2 (n ‘mod‘ m, m)
gcd2 :: Integral a => (a,a) -> a
Example (2 arguments, no parentheses)
gcd1 m n = if m == 0 then n
else gcd1 (n ‘mod‘ m) m
gcd :: Integral a => a -> a -> a
Some more type inference examples
Example (1 argument)
type vect = int * int;
fun gcd3 ((m,n):vect) = if m = 0 then n
else gcd3 (n mod m, m);
val gcd3 = fn : vect -> int
Example (2 arguments, perantheses)
fun gcd2 (m, n) = if m = 0 then n
else gcd2 (n mod m, m);
val it = fn : int * int -> int
Some more type inference examples
Example (2 arguments, no parentheses)
fun gcd1 m n = if m = 0 then n
else gcd1 (n mod m) m;
val gcd1 = fn : int -> int -> int
Defining lists
Definition
A list is a sequence of values of a specific type.
Example
[1,2,3]
["RO", "Pl" , "Dk"]
[]
[[1,2], [3,4,5]]
[(1,’’one’’), (2,’’two’’)]
Defining lists
Definition
A list is either empty, or is an ordered pair made of an element
(“head”) and a list (“tail”).
Observation
This definition shows the connection between data and control.
Constructors
Haskell operator : (right associative)
Example
Prelude> 1:2:3:[]
[1,2,3]
Example
Prelude> 1:2:[3]
[1,2,3]
ML operator :: (right associative)
Example
> 1::2::3::[];
val it = [1, 2, 3] : int list
List building
Generating a sequence of integers:
Example
fun inter(m,n) = if m > n then []
else m::inter (m+1,n);
> inter(2,7);
val it = [2, 3, 4, 5, 6, 7] : int list
List processing: recursion + pattern matching
Example (Define a function using patterns)
fun prodl [] = 1
| prodl (n::ns) = n * prodl ns;
Observations
1. | means pattern separation; pattern order matters
2. x::xs matched on [1,2,3] binds x to 1 and xs to
[2,3]
3. 1::xs matched on [1,2,3] binds xs to [2,3];
4. 1::xs fails when attempting to match [2,3]
5. x::_::xs matched on [1,2,3,4] binds x to 1 and xs to
[3,4]
List processing without patterns
Example
fun prodl [] = 1
| prodl (n::ns) = n * prodl ns;
Example (Equivalent function with no patterns)
> fun prodl ns = if null ns then 1
# else hd ns * prodl (tl ns);
Observations
1. using patterns increases code clarity ...
2. ... and allows a better analysis of cases, thus leading to a
better performance of the generated code
List processing: maxl and null
Example
> fun maxl [x] = x
# | maxl (x::y::ys) = if x>y then maxl(x::ys)
# else maxl(y::ys);
Warning: Matches are not exhaustive.
Found near fun maxl([ x]) = x | maxl(......) =
if ...... then ...(...) else ...(...)
val maxl = fn : int list -> int
Example
fun null [] = true
| null (_::_) = false;
List processing: head
Example
fun hd(x::_) = x;
Warning: Matches are not exhaustive.
Found near fun hd(::( ...)) = x
val hd = fn : ’a list -> ’a
Example
> hd [1,2,3];
val it = 1 : int
List processing: tail
Example
> fun tl (_::xs) = xs;
Warning: Matches are not exhaustive.
Found near fun tl(::( ...)) = xs
val tl = fn : ’a list -> ’a list
Example
> tl [1,2,3];
val it = [2, 3] : int list
List processing: length and append
Example
my_length [] = 0
my_length (_:xs) = 1 + my_length xs
Example
infix 4 +++
[] +++ ys = ys
(x:xs) +++ ys = x:(xs +++ ys)
List processing: take and drop
Example
my_take 0 _ = []
my_take k [] = []
my_take k (x:xs) = x:my_take (k-1) xs
Example
my_drop 0 xs = xs
my_drop k [] = []
my_drop k (x:xs) = my_drop (k-1) xs
List processing: reverse
Example
my_rev0 [] = []
my_rev0 (x:xs) = my_rev0 xs ++ [x]
or:
Example
my_rev1 xs = my_rev1acc xs []
where
my_rev1acc [] ys = ys
my_rev1acc (x:xs) ys = my_rev1acc xs (x:ys)
Composed lists: flattening lists
Example
fun flatten [] = []
| flatten (x::xs) = x @ flatten xs;
Example
> flatten [["Ro","Pl"], ["Dk"], ["Me"]];
val it = ["Ro", "Pl", "Dk", "Me"] : string list
Composed lists: zip and unzip
Example
fun zip ([], []) = []
| zip (x::xs, y::ys) = (x,y)::zip(xs,ys);
> zip (["Ro", "Pl", "Dk"], [1,2,3]);
val it = [("Ro", 1), ("Pl", 2), ("Dk", 3)]
Example
fun unzip [] = ([], [])
| unzip ((x,y)::zs) =
let val (xs,ys) = unzip zs
in (x::xs,y::ys) end;
Composed lists: zipWith and sum
Example (Summing up a list)
Prelude> sum [2,3,4]
9
Example (A higher order function)
Prelude> zipWith (*) [1,2,3] [4,5,6]
[4,10,18]
Association lists
Definition
Association list = a list of pairs (key, value) with key of type Eq.
Example
val capitals = [("Romania", "Bucharest"),
("Poland", "Warsaw"),
("Denmark", "Copenhagen")];
Association lists
Example
val capitals=[("Romania","Bucharest"),
("Poland","Warsaw"),
("Denmark","Copenhagen")];
fun assoc (x, []) = []
| assoc (x, (y,z)::lp) = if x = y then [z]
else assoc (x, lp);
Example
> assoc ("Romania", capitals);
val it = ["Bucharest"] : string list
DAGs as association lists
Example (A dag)
val dag=[("a","b"),("a","c"),("a","d"),
("b","e"),("c","f"),("d","e"),
("e","f"),("e","g")];
Example (All successors of a node)
fun succ(x, []) = []
| succ(x,(y,z)::lp) = if x=y then z::succ(x,lp)
else succ(x,lp);
Example (All successors of a node)
> succ ("a", dag);
val it = ["b", "c", "d"] : string list
A problem by Euler
In how many ways can we triangulate a convex polygon by
non-intersecting diagonals?
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
G D
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
G D
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
T6 ∗ T3 +
G D
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
T6 ∗ T3 +
T5 ∗ T4 +
G D
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D T3 ∗ T6 +
H C
A B
Counting triangulations Tn in a systematic way
F E T3 = 1, T4 = 2
T8 = T7 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D T3 ∗ T6 +
T7
H C
A B
Counting triangulations Tn in a systematic way
F E T2 = 1, T3 = 1, T4 = 2
T8 = T2 ∗ T7 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D T3 ∗ T6 +
T7 ∗ T2
H C
A B
Counting triangulations Tn in a systematic way
F E T2 = 1, T3 = 1, T4 = 2
T8 = T7 ∗ T2 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D T3 ∗ T6 +
T2 ∗ T7
8−1
X
H C T8 = Ti ∗ T8+1−i
i=2
A B
Counting triangulations Tn in a systematic way
F E T2 = 1, T3 = 1, T4 = 2
T8 = T7 ∗ T2 +
T6 ∗ T3 +
T5 ∗ T4 +
T4 ∗ T5 +
G D T3 ∗ T6 +
T2 ∗ T7
8−1
X
H C T8 = Ti ∗ T8+1−i
i=2
n−1
X
Tn = Ti ∗ Tn+1−i
i=2
A B
Computing Tn
Formula
n−1
X
Tn = Ti ∗ Tn+1−i
i=2
Haskell code v1: straight, but not efficient
trgls n
| n <=3 = 1
| otherwise = sum $ [(trgls i)*(trgls (n+1-i))
| i <- [2..(n-1)] ]
Computing Tn
Haskell code v2: more efficient
trgls2 n = tracc n 1 [1]
where
tracc n k cs
| k == n-1 = head cs
| otherwise = tracc n (k+1) (cn:cs)
where cn = sum $ (zipWith (*)
cs (reverse cs))
Summary
I call-by-need (arguments evaluated just once, when
needed) and call-by-value (arguments evaluated before
being processed) have both advantages and disavantages
I polymorphic functions are present in FP as well, which
provides more flexibility
I a basic repertoire of list functions comprises constructors,
a null test, head, tail, append and length
That’s all, folks!
Thanks for your attention...Questions?