0% found this document useful (0 votes)
15 views53 pages

CS571 sp24 Lecture15

The lecture discusses the features, advantages, and disadvantages of logic programming and introduces functional programming concepts, including core constructs, control flow, and higher-order functions. It emphasizes the importance of purity, referential transparency, and lazy evaluation in functional programming, particularly using Haskell as the primary language. Key examples include function definitions, conditional expressions, and the implementation of common functions like factorial and map.

Uploaded by

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

CS571 sp24 Lecture15

The lecture discusses the features, advantages, and disadvantages of logic programming and introduces functional programming concepts, including core constructs, control flow, and higher-order functions. It emphasizes the importance of purity, referential transparency, and lazy evaluation in functional programming, particularly using Haskell as the primary language. Key examples include function definitions, conditional expressions, and the implementation of common functions like factorial and map.

Uploaded by

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

CS 571 Lecture 15

March 27, 2024


Farewell to Logic Programming
• Provides features not present in any other programming paradigm
• Backtracking search
• Unification
• Advantages
• Good for logic problems
• And recursive descent parsing
• Or anything else where you need backracking search or unification
• Disadvantages
• Hard to reason about execution
• Difficult to anticipate program runtime and termination
Functional Programming
• Introduction to Functional Programming
• Basic Functional Programming
• Core functional programming concepts
• Types, type classes, and algebraic data types
• Mutable State and Monads
• Advanced Functional Programming
Functional Programming Agenda
• Basic Functional Programming
• Functions
• Let binding
• Control flow
• Factorial example
• Core Functional Programming Concepts
• Higher-order functions & Closures
• Referential Transparency & Purity
• Lazy Evaluation
Basic Functional
Programming
Functional Programming
• Functional programming emphasizes the application of functions, in
contrast to imperative programming

• A functional language is a language that supports and encourages


programming in a functional style
• There are many functional languages out there, and they are on the
rise.
• Python, Java, and C++ now have functional features.
• Ocaml, Scala, and Haskell are more functionally oriented.
• In this class, we will study Haskell
Factorial in Haskell

fact x = if x == 0 then 1 else x * fact(x-1)


Haskell Programs vs. Maths
Functions

Writing a Haskell function is like doing


mathematics!
Core Language Constructs
• How to create a function? (function definition)
fact x = if x == 0 then 1 else x * fact(x-1)

• How to use a function? (function application)

fact 3
Building Bigger Programs

let fact x = if x == 0 then 1 else x * fact(x-1) in


fact 3
Building Bigger Programs

let x = 3 + 2 in
let y = 4 in
x+y
Is Similar to…

is similar to C code:

{ int x = 3+2;
{ int y = 4;
return x+y;
}
}
No Mutation
• let x = 3 + 2 in let x = 4 in x
is similar to C code:

{ int x = 3+2;
{ int x = 4;
return x;
}
}

• let x = 3 + 2 in (let x = 4 in x) + x
let…in… Expression
• The let expression provides a way to bind names to
values/expressions.
let x = 3 + 2 in x^2
• The binding also allows for encoding top-level function definitions
into directly evaluable expressions.
let inc x = x + 1 in inc 2
• Nested let expressions provide continuation, i.e. “stringing
expressions together”
let x = 3 + 2 in let y = 4 in x^2 + y
let…in… Expression (Cont.)
• One might think it is the same as assignments in C-like languages, but
note that let-bindings are immutable (so much so that some purists don’t
call “x” or “y” as variables…)
• The expression also introduces non-global scoping: it is also possible to
make definitions local to the expression.
f y = let x=3+2 in x^2+y
f 1 giving the result 26.

• A syntactically sugared form of nested let expressions is the ‘;’ expression:


f z = let x = 3+2; y = 4 in x+y+z
Functions as Values
let funcB y f = y * (f y ) in
let funcA x = 2*x in
funcB 3 funcA
Functions as First-Class Citizens

• Functions are values; values can be passed as arguments to functions


• functions can be passed as arguments to functions!
• If you are trying to find an equivalent in C, that is function pointers.
But Haskell (and most functional programming languages can deal
with it in a MUCH MORE principled manner)
Control Flow
Conditional Expressions
• We can write conditional expressions by means of the if…then…else
constructor of Haskell.

max:: Int  Int  Int


max x y
= if x >= y then x else y
Guards/Conditions
• Used to give alternatives in the definitions of functions.

• A guard is a Boolean expression and these expressions are used to


express various cases in the definition of a function.

max:: Int  Int  Int


max x y
| x >= y = x
| otherwise = y
Example: maxThree
• Given three inputs, compute the maximum number.
maxThree:: Int  Int  Int  Int
maxThree x y z
| x >= y && x >= z = x
| y >= z = y
| otherwise = z
Factorial Revisited

factorial :: Int  Int


factorial x
| x < 0 = error “negative value”
| x == 0 = 1
| otherwise = x * factorial (x-1)
Factorial Revisited
factorial :: Int  Int
factorial x
| x < 0 = error “negative value”
| x == 0 = 1
| otherwise = x * factorial (x-1)

Main> factorial (-1)


Program error: negative value

Main> factorial 4
24

Main> factorial 4.5


ERROR - Cannot infer instance
*** Instance : Fractional Int
*** Expression : factorial 4.5
Factorial Revisited (Cont.)
• If we do not specify the type of fact, then Haskell picks up the most general type

fact x
| x < 0 = error “negative value”
| x == 0 = 1
| otherwise = x * fact (x-1)
… > : t fact
fact :: (Ord a, Num a) => a  a
The input must be in the intersection of classes Ord (Bool, Char, Int, Integer, Float, Double etc.)
and Num.
…> fact 3.7
Program error: negative value
Factorial Revisited

factorial :: Int  Int


factorial x

| x == 0 = 1
| otherwise = x * factorial (x – 1)
Factorial Revisited Revisited

factorial :: Int  Int


factorial x =
case x of
0 -> 1
_ -> x * factorial (x – 1)
Factorial Revisited Revisited
Revisited

factorial :: Int  Int

factorial 0 = 1
factorial x = x * factorial (x – 1)
Functional Programming Basics:
Summary
• All programs are functions

• Build larger programs with let ... in statements

• Control Flow
• If statements
• Guards
• Case statements
• Multiple definitions
Key Concepts in Functional
Programming
• Higher-order functions & closures

• Purity & referential transparency

• Laziness
• Call-by-name vs. call-by-value
Higher-order functions
& Closures
Functions as Values
• Takes a function as an argument or returns a function as a result, or
both

• When a function takes another function as an argument (or returns it


as a result) the host function is called a higher-order function
Higher-Order Function: map
• map: takes in two inputs - a function, and a list. It then applies this
function to every element in the list.
map f [] = []
map f (x:xs) = f x : map f xs

map f [x1,x2,x3]
= f x1 : map f [x2,x3]
= f x1 : f x2 : map f [x3]
= f x1 : f x2 : f x3 : map f []
= f x1 : f x2 : f x3 : []
Higher-Order Function: map
doubleAll [] = []
doubleAll (x:xs) = 2*x : doubleAll xs

• Use map to implement doubleAll

map f [] = []
map f (x:xs) = f x : map f xs
double x = 2*x
doubleAll xs = map double xs

Alternatively: doubleAll = map double


Alternatively: doubleAll = map (\x -> 2*x)
Alternatively: doubleAll = map (2*)
Nameless Functions
• Instead of naming and defining a function, we can write it down directly.
\n -> 2*n
• The following expressions of f definition are equivalent:
let f x y = x+y in f 1 2
let f = \x -> (\y -> x + y) in f 1 2
• In fact, nameless function is the “more core” construct in Haskell.
Named functions are nothing but a nameless function with a name
binding using let, and let is also syntactic sugar for functions:
(\f -> f 1 2) (\x -> (\y -> x + y))
Closures Equivalently:

squareAndAdd x y =
squareAndAdd x = let x2 = x * x in
let x2 = x * x in x + y
\y -> x2 + y

squareThreeAndAdd = squareAndAdd 3
squareThreeAndAdd 1
squareThreeAndAdd 2 Outputs 10

Outputs 11
Where is the “hidden information” that x = 3 stored?
How to Read this Exactly?

add :: Int -> Int -> Int


add x y = x+y
Currying a Function
• Functions of two or more arguments
• Curried form -- the name curry derives from the person who popularized the idea:
Haskell Curry
add :: Int -> Int -> Int
is right associative, a  b  c means a  (b  c)
add x y = x+y is equivalent to add = \x -> (\y -> x+y)
add x y is equivalent to (add x) y - applying add to
yields a new function which is then applied to the
second argument y

We can define inc using add:


inc = add 1
Currying a Function
• Functions of two or more arguments
• Uncurried form
addUC :: (Int, Int)  Int
addUC (x,y) = x+y
Hugs> :load adduc.hs
Main> addUC(1,2)
3
Purity & Referential
Transparency
Referential Transparency
• An expression e is referentially transparent iff
• The only thing that matters about e is its value

• Replacing any subexpression of e with another equally valued


expression has no effect on the value of e
• Every occurrence of e in scope has the same value
• Haskell expressions are referentially transparent
• C/C++/Java/C# allow referentially opaque expressions
int i = 3;
int n = i++ - i ++ // 3 – 4 = -1
Purity in Functional Programming
• Referential transparency is equivalent to the concept of purity in
functional programming
• A pure function is one that does not have side effects (aka mutation)

int length(List *lst) { length [] = 0


int length = 0; length (_:rest) =
while(lst != null) { 1 + length(rest)
length++;
lst = lst->next;
} Side effects!
}
Laziness
Lazy Evaluation
• Haskell uses lazy evaluation: parameters are not evaluated at time-of-
call; instead they are evaluated when needed within called function.
• Laziness requires purity.

• Laziness Example
Lazy Evaluation
takeVal (naturalsLargerThan 1) 5
= (head (naturalsLargerThan 1)) : takeVal ...
= (head (1 : naturalsLargerThan(1+1)) : takeVal ...
= 1 : takeVal ...
= 1 : takeVal (tail (naturalsLargerThan 1)) 4
= 1 : head (tail (naturalsLargerThan 1)) : takeVal ...
= 1 : head (naturalsLargerThan (1+1)) : takeVal ...
= 1 : head (naturalsLargerThan (2)) : takeVal ...
= 1 : head (2 : naturalsLargerThan(2+1)) : takeVal ...
= 1 : 2 : takeVal ...
= ...
= 1 : 2 : 3 : 4 : 5
Lazy Execution Model
Capture Avoiding Substitution
• To execute (\x. A) (B), replace it with the expression A with all
instances of x in A substituted to B
• E.g. (\x. x + 1) 3 => 3 + 1 => 4

• Need to be careful to avoid capturing x


• E.g. (\x. (\x. x + 1) x + 1) 3
• Naïve substitution would give (\x. 3 + 1) 3 + 1, which is wrong
Capture Avoiding Substitution
• Solution -renaming
• In general, (\x. x + 1) is equivalent to (\y. y + 1)

(\x. (\x. x + 1) (x + 1)) 3


=> (\x. (\y. y + 1) (x + 1)) 3
=> (\y. y + 1) (3 + 1)
=> (\y. y + 1) (4)
=> 4 + 1
=> 5
Revisiting Parameter Passing
• Applicative Order/Innermost
Evaluation Policy
• Evaluate each argument to 2 + (\x -> (x + 3) / (6 - x)) (7 - 2)
obtain a value
2 + (\x -> (x + 3) / (6 - x)) 5
• Substitute each argument value
for each occurrence of the 2 + (5 + 3) / (6 - 5)
corresponding parameter in the
function body 2 + 8 / 1

• Evaluate the function 10

• A.k.a. “strict” or “eager”


execution
• Evaluation in every language
we’ve seen so far
Revisiting Parameter Passing
• Normal 2 + (\x -> (x + 3) / (6 - x)) (7 - 2)
Order/Outermost 2 + ((7 - 2) + 3) / (6 - (7 - 2))
Evaluation Policy
• Substitute each 2 + (5 + 3) / (6 - (7 - 2))
unevaluated argument
2 + 8 / (6 - (7 - 2))
for each
corresponding 2 + 8 / (6 - 5)
parameter in the body
2 + 8 / 1

• Evaluate the resulting 2 + 8


expression
10
• Lazy execution as in
Haskell
Revisiting Parameter Passing
• Normal order always succeeds if applicative order does, and normal
order succeeds in some cases where applicative order does not.

Normal order Applicative order


(\x y -> if x then 7 else y) (2 > 1) (9 / 0) (\x y -> if x then 7 else y) (2 > 1) (9 / 0)

if 2 > 1 then 7 else 9/ 0 (\x y -> if x then 7 else y) True ⊥


7 ⊥

We use ⊥ (bottom) to denote the “value” of expressions that do not terminate or that error.
Revisiting Parameter Passing

Normal order is often less efficient. cube = \x -> x * x * x


Normal order Applicative order
cube (cube 2) cube (cube 2)
cube 2 * cube 2 * cube 2 cube (2 * 2 * 2)
2 * 2 * 2 * cube 2 * cube 2
cube (4 * 2)
4 * 2 * cube 2 * cube 2
cube 8
8 * cube 2 * cube 2
8*8*8
8 * (2 * 2 * 2) * cube 2 64 * (2 * 2 * 2) 64 * 8
8 * (4* 2) * cube 2 64 * (4* 2) 512
8 * 8 * cube 2 64 * 8
64 * cube 2 512
Practice Question
• Would the following programs terminate without error under lazy or strict execution?

let x = x + 1 in
✅ ❌
4+2
let x = x + 1 in
True || x == 5 ✅ ❌

let x = \y -> (x y) + 1 in
4+2 ✅ ✅

let x = \y -> (x y) + 1 in
✅ ¯\_( ツ )_/¯
True || (x 0) == 5

let x = x + 1 in
x == 5 ❌ ❌
Acknowledgments
• William Hallahan
• David Liu
• Zerksis Umrigar

You might also like