0% found this document useful (0 votes)
30 views

FP c03 NoAnim

The document provides an overview of functional programming concepts related to parameter evaluation methods like call-by-value and call-by-name, polymorphic types, and lists. It discusses how call-by-value evaluates parameters before function calls while call-by-name evaluates them inside the function. It also covers call-by-need which is similar to call-by-name but only evaluates expressions once. The document defines polymorphic types and type inference in functional programming. It concludes with examples of working with lists and association lists in ML and Haskell.

Uploaded by

Ioana Turean
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views

FP c03 NoAnim

The document provides an overview of functional programming concepts related to parameter evaluation methods like call-by-value and call-by-name, polymorphic types, and lists. It discusses how call-by-value evaluates parameters before function calls while call-by-name evaluates them inside the function. It also covers call-by-need which is similar to call-by-name but only evaluates expressions once. The document defines polymorphic types and type inference in functional programming. It concludes with examples of working with lists and association lists in ML and Haskell.

Uploaded by

Ioana Turean
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 70

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?

You might also like