CS1010S Programming Methodology
Lecture 2
Functional Abstraction
23 Aug 2023
Admin Matters
• Recitation classes start tomorrow.
• Use of ChatGPT
• Recitations/Tutorials on 1st Sept.
Don’t Stress
Please do your work
Try not to submit at 23:58
DO NOT plagiarize!
Quick Revision!
Variables
•Each variable has:
•Name
•Value
•Address
•Every variable can
hold data of a single
kind at a given time.
Operators
Assignment
a = 5
Equality testing
a == 5
Not equal
a != 5
#Comments
# this is not a hashtag!
>>> print("Good to go")
"Good to go"
#print("Good to go")
# whatever is after the # is ignored
Python Imaging Library
What's this?
from PIL import *
(Mission 0)
CS1010S Road Map
Searching & Sorting
Object-Oriented ADVANCED
Programming
Higher-Order Multiple INTERMEDIATE
List
Procedures Processing Representations
Functional Data BASIC
Iteration Mutation &
Abstraction
Abstraction State
Wishful Recursion
Order of
Thinking Growth
Fundamental concepts of computer programming
Functional Abstraction
What is a function?
Inputs Function Output
Functions aren’t new to us!
𝜃
𝑎
Find x?
𝑎 = 𝑥 cos(𝜃)
function input
Question:
How do we square a
number?
Define Name Input
𝑥 def square(x):
Square
𝑥2
return x * x
Return Output
square(21) 441 def square(x):
return x * x
square(2 + 5) 49
square(square(3)) 81
𝑥 𝑥2 + 𝑦2
𝑦 sum_of_squares
def sum_of_squares(x, y):
return square(x) + square(y)
sum_of_squares(3, 4) 25
𝑎
𝑏 sum_of_squares 𝑐
from math import sqrt
c
a
def hypotenuse(a, b):
return sqrt(sum_of_squares(a, b))
b
hypotenuse(5, 12)
13
Syntax of writing a function!
def <name> (<parameters>):
<body>
Name
- Symbol associated with the function
Parameters (inputs)
- Names used in the body to refer to the arguments of the function
Body
- The statement(s) to be evaluated
- Has to be indented (standard is 4 spaces)
- Can return values as output
Definition versus Invocation
Definition def square(x):
return x * x
def <name> (<parameters>):
<body>
square(5)
Invocation square(6)
<name> (<parameters>)
Definition versus Invocation
def fun(x): return x / 0
Will this raise an error?
fun(2)
The body of the function is NOT executed until it is called!
If the code block consists
of the single line, it can
be written on the same
line after the :
Function – a black box
Inputs Function Output
OutputDon’t
is returned
need towith
know how it statement
return works
Return
Just know
typewhat
can be
it does
None
Functional Abstraction
Managing Complexity
Functional Abstraction
Primitives
Problem Solution
Abstractions
Invented to make
the task easier
What makes a good abstraction?
def multiply(a, b):
ans = 0
count = 1
while count <= b:
ans = ans + a Operators in Python are
already an abstraction for
count = count + 1 the programmer!
return ans
multiply(a, b)
Why functions?
Makes it more natural to think
about tasks and subtasks
House Bricks
Rooms
Divide and
Walls
Conquer
Bricks
Program Primitives
Functions
Divide and
Conquer
Primitives
Why functions?
Makes program easier to
understand
def hypotenuse(a, b):
return sqrt((a*a) + (b*b))
def hypotenuse(a, b):
return sqrt(sum_of_squares(a, b))
def sum_of_squares(x, y):
return square(x) + square(y)
def square(x):
return x * x
Why functions?
Captures common patterns
Examples coming soon!
Why functions?
Allows for code reuse
Another Example
Function to calculate area of circle given the radius
pi = 3.14159
Imagine… If we hadn’t
def circle_area_from_radius(r): had most commonly used
return pi * square(r) operators such as
*, /, //, %!
given the diameter:
def circle_area_from_diameter(d):
return circle_area_from_radius(d/2)
Why functions?
Hides irrelevant details
Ok for some
chemical analyses,
inadequate for
others.
Water molecule
represented as 3 balls
No need to know how a car works to drive it!
variable Function
print value to the output!
Do I really know how print works?
Why functions?
what
Separates specification from
implementation
how
def square(x):
return x * x
Why would we want
to implement a
def square(x): function in different
return exp(double(log(x))) ways?
def square(x): (Lec 4)
return sqrt(x**4)
def square(x):
return x**2
Why functions?
Makes debugging easier
def hypotenuse(a, b):
return sqrt((a + a) * (b + b)) Where is the bug?
def hypotenuse(a, b):
return sqrt(sum_of_squares(a, b))
def sum_of_squares(x, y):
return square(x) + square(y)
def square(x): return x + x
Functional abstraction (Summary)
1. Divides the messy problem into natural subtasks
2. Makes program easier to understand
3. Captures common patterns
4. Allows code reuse
5. Hide irrelevant details
6. Separates specification from implementation
7. Makes debugging easier
Scope of variables
x = 10
def square(x): return x * x
def double(x): return x + x
def addx(y): return y + x
• square(20)
• square(x) Which x ?
• addx(5)
Scope of variables
formal parameter
def square(x):
return x * x body
A function definition binds its formal
parameters.
i.e. the formal parameters are visible only inside
the definition (body), not outside.
Scope of variables
formal parameter
def square(x):
return x * x body
• Formal parameters are bound variables.
• The region where a variable is visible is called
the scope of the variable.
• Any variable that is not bound is free.
Scope of variables
x = 10
def square(x):
return x * x x is bound
def double(x):
x is bound
return x + x
def addx(y): y is bound, x is free
return y + x
Example Global
x = 10
y = 20
x, y = 10, 20 square
double
def square(x): return x * x addx
def double(x): return x + x
def addx(y): return y + x square square
x = 20 x = 10
return = 400 return = 100
square(20)
square(x)
addx
addx(5) y=5
return = 15
Example Global
Global
a=3
a=3
b=4
b=4
a, b = 3, 4 hypotenuse
hypotenuse
c=5
def hypotenuse(a, b):
def sum_of_squares():
return square(a) + square(b)
hypotenuse
return [Link](sum_of_squares()) hypotenuse
a=4
a=4
b=3
b=3
sum_of_squares
sum_of_squares
c = hypotenuse(4, a) return=5
[Link] sum_of_squares
return = 5 return = 25
Abstract Environment
Picture Language
([Link])
Also [Link] + [Link]
Primitives: show
show(rcross_bb)
show(corner_bb)
Picture object
show(sail_bb)
show(nova_bb)
show(heart_bb)
Primitives are functions!
picture show
Primitives: quarter_turn_right
operation picture
clear_all()
show(quarter_turn_right(sail_bb))
result is
another picture
picture quarter_turn_right picture
show
Derived: turn_upside_down
def turn_upside_down(pic):
return quarter_turn_right(
quarter_turn_right(pic))
clear_all()
show(turn_upside_down(sail_bb))
picture quarter_turn_right picture
turn_upside_down
picture quarter_turn_right
Derived: quarter_turn_left
def quarter_turn_left(pic):
return quarter_turn_right(
quarter_turn_upside_down(pic))
clear_all()
show(quarter_turn_left(sail_bb))
Primitive: stack picture1
stack picture3
picture2
clear_all()
show(stack(rcross_bb, sail_bb))
Derived multiple stacking
clear_all()
picture1 picture3
stack
show(stack(rcross_bb, picture2
stack(rcross_bb, picture5 stack
sail_bb) ) picture4
Derived: beside
def beside(pic1, pic2):
return quarter_turn_left(
stack(quarter_turn_right(pic2),
quarter_turn_right(pic1)))
Derived – a more complex function!
clear_all()
show(
stack(
beside(
quarter_turn_right(rcross_bb),
turn_upside_down(rcross_bb)),
beside(
rcross_bb,
quarter_turn_left(rcross_bb))))
Let's give it a name make_cross
How to write make_cross function?
def make_cross(pic):
stack( return stack(
beside( beside(
quarter_turn_right(rcross_bb), quarter_turn_right(pic),
turn_upside_down(rcross_bb)), turn_upside_down(pic)),
beside( beside(
rcross_bb, pic,
quarter_turn_left(rcross_bb)))) quarter_turn_left(pic))))``
Storing the return values!
my_pic = make_cross(sail_bb)
show(my_pic)
my_pic_2 = make_cross(nova_bb)
show(my_pic_2)
Repeating the pattern
clear_all()
show(make_cross(make_cross(nova_bb)))
Repeating the pattern pat(pat(pat(pat(pic))))
pat(pat(pat(pic)))
def repeat_pattern(n, pat, pic): pat(pat(pic))
if n == 0: pat(pic)
return pic recursion pic
else:
return pat(repeat_pattern(n-1, pat, pic)) repeat_pattern(4)
pat(repeat_pattern(3))
show(repeat_pattern(4, make_cross, nova_bb))
pat(repeat_pattern(2))
pat(repeat_pattern(1))
pat(repeat_pattern(0))
repeat_pattern(0)
clear_all()
show(repeat_pattern(4, make_cross, rcross_bb))
Anonymous(aka lambda) functions
def square(x):
return x * x
input output
foo = lambda x: x * x
function
foo(1) 1
foo(4)
16
New patterns!
anonymous
show(repeat_pattern(3, function
lambda pic: beside(pic, pic),
nova_bb))
clear_all()
show(repeat_pattern(3,
lambda pic: stack(pic, pic),
nova_bb))
Primitive: stack_frac
clear_all()
show(stack_frac(1/3, rcross_bb, sail_bb))
Repeating the pattern
def stackn(n, pic):
if n == 1:
return pic
else:
return stack_frac(1/n,
pic,
stackn(n-1, pic))
clear_all()
show(stackn(3, nova_bb))
clear_all()
show(stackn(5, nova_bb)) Homework!
One final pattern!
clear_all()
show(stackn(5, quarter_turn_right(
stackn(5, quarter_turn_left(nova_bb)))))
No idea how a picture is
rcross_bb
represented! sail_bb
sail_bb heart_bb
Data Abstraction
No idea how the operations
actually do their work
show quarter_turn_right stack
Functional Abstraction
Yet, we can build
complex pictures
repeat_pattern(4, make_cross, rcross_bb)
Functional Abstraction
Wishful Thinking
Pretend you have whatever you need
Another example: Taxi Fare Calculation
NTUC Comfort, the largest taxi operator in Singapore, determines the
taxi fare based on distance traveled as follows:
• For the first kilometre or less: $2.40
• Every 200 metres thereafter or less up to 10 km: $0.10
• Every 150 metres thereafter or less after 10 km: $0.10
Problem:
Write a Python function that
computes the taxi fare from distance
travelled.
How do we start?
Formulate the problem
Function
Needs a name
Pick an appropriate name
(not foo)
Formulate the problem
distance Taxi Fare fare
• What data do • Results should be
you need? unambiguous
• Where would
you get it? • What other abstractions
may be useful?
• Ask the same questions for
each abstraction.
How to compute the result?
1. Try simple examples
2. Strategize step by step
3. Write it down and refine
How to compute the result?
• e.g. #1: distance = 800 m, fare = $2.40
• e.g. #2: distance = 3,300 m
fare = $2.40 + 2300/200 × $0.10
= $3.60
• e.g. #3: distance = 14,500 m
fare = $2.40 + 9000/200 × $0.10 + 4500/150 × $0.10
= $9.90
Solution
• What to call the function? taxi_fare
• What data are required? distance
• Where to get the data? function argument
• What is the result? fare
Pseudocode
Case 1: distance <= 1000
fare = $2.40
Case 2: 1000 < distance <= 10,000
fare = $2.40 + $0.10 * (distance – 1000)/200
Case 3: distance > 10,000
fare = $6.90 + $0.10 * (distance – 10,000)/150)
Note: the Python function ceil rounds up its argument.
[Link](1.5) = 2
Program
def taxi_fare(distance): # distance in metres
if distance <= 1000:
return 2.4
elif distance <= 10000:
return 2.4 + (0.10 * ceil((distance – 1000) / 200))
else:
return 6.9 + (0.10 * ceil((distance – 10000) / 150))
# check: taxi_fare(3300) = 3.6
•Can we improve this solution?
Generalisability?
What if…
1. the starting fare increases?
2. Stage distance changes?
3. Increment amount changes?
Avoid magic numbers!
It is a terrible idea to hardcode numbers (magic
numbers):
• Hard to make changes in future
Define abstractions to hide them!
def taxi_fare(distance): # distance in metres
if distance <= stage1:
return start_fare
elif distance <= stage2:
return start_fare +
(increment * ceil((distance - stage1) / block1))
else:
return taxi_fare(stage2) +
(increment * ceil((distance - stage2) / block2))
stage1 = 1000
stage2 = 10000
start_fare = 3.2
increment = 0.22
block1 = 400
block2 = 350
Summary Why functions are good!
• Functional Abstraction 1. Divides the messy problem into natural
• Good Abstractions subtasks
• Variable Scoping 2. Makes program easier to understand
• Wishful Thinking 3. Captures common patterns
4. Allows code reuse
5. Hide irrelevant details
6. Separates specification from
implementation
7. Makes debugging easier