Last updated on
2024-09-23 debrief: Schedule changes, boids
lab, and equational reasoning
Congratulations on completing your second week of CS-214! Here is a round-up of interesting questions, tips for exercises and labs, and general notes about the course.
Administrivia
- Changes to the help sessions:
- To help you finish each lab before the next one is released, we’re adding a new help session on Tuesdays 4–7PM, in INF 1, 2, and 3.
Just like regular help sessions on Wednesdays and Fridays, these help sessions are optional: they are just an additional opportunity for you to get help. - On Wednesdays, we have added INF 3 and removed ELA 2 and ELD 120.
- On Fridays, due to low attendance, we have removed CO 4,5,6.
- To help you finish each lab before the next one is released, we’re adding a new help session on Tuesdays 4–7PM, in INF 1, 2, and 3.
- The syllabus has been updated to cover the entire semester.
boids
is due on Friday.
Interesting links and Ed questions
- Higher order functions:
- Scala:
boids
lab
Reflecting on recursion and HOF exercises
What should product
and allEven
return for empty lists?
Here are two ways to think of these:
Simplicity
You could make these functions throw an exception for empty lists. Here is what this would look like for product
:
def product(l: IntList):
if l.isEmpty then throw new InvalidArgumentException("Empty!")
else if l.tail.isEmpty then l.head
else l.head * product(l.tail)
But! Perhaps we could make this nicer if we allowed ourselves to return a different value for empty lists. What should this value be?
def product(l: IntList):
if l.isEmpty then ???
else l.head * product(l.tail)
Let’s see by substitution:
product(IntCons(3, IntNil))
3 * product(IntNil)
3 * ???
So ???
should be such that 3 * ??? = 3
. Hence, product(IntNil)
should be 1
!
Desirable properties
Another way to think of this issue is to think of what properties we want our functions to have:
-
allEven(L)
should be the same asnot(anyOdd(L))
. Are there any odd numbers in the empty list? No, soanyOdd(L)
is false, and henceallEven(L)
must be true! -
Using
A ++ B
to denote the concatenation of two listsA
andB
, what should be the value ofproduct(A ++ B)
? Intuitively, we would like it to beproduct(A) * product(B)
.Now take
B == IntNil
. ThenA ++ B
is simplyA
, and henceproduct(A) = product(A ++ B) = product(A ++ IntNil) == product(A) * product(IntNil)
, which means thatproduct(IntNil)
must be1
!
Using equational reasoning to simplify allPositiveOrZero
In the recursion exercise set, some of you ended up with the following definition for allPositiveOrZero
:
def allPositiveOrZero(l: IntList): Boolean =
if !l.isEmpty then
if l.head < 0 then false
else allPositiveOrZero(l.tail)
else true
equational.worksheet.sc
That’s pretty heavy! There are two nested if ... then ... else ...
blocks in there.
Let’s see how we might improve this a bit, using equational reasoning.
What is equational reasoning? In Algebra, if we know that the following identity is true: $$ (a + b)^2 = a^2 + 2ab + b^2 $$ then we can immediately know that $$ (xz + y^2)^2 = (xz)^2 + 2xzy^2 + (y^2)^2 $$ is also true, by substituting in $a \mapsto xz$ and $b \mapsto y^2$. We can do the same thing when writing functional programs!
Here are a few identities that we can show, with Boolean values:
!!c
=c
if c then t else e
=if !c then e else t
!(x < 0)
=x >= 0
if c then false else e
=!c && e
if c then e else true
=???
(try to fill in the blank!)
Do you know how to prove these? (Hint: remember truth tables, from ICC?)
Now, back to allPositiveOrZero
. Can you use any of the identities above to simplify the code?
Step 1
if l.head < 0 then false
else allPositiveOrZero(l.tail)
equational.worksheet.sc
is exactly our identity (4), if we substitute c
with l.head < 0
and e
with allPositiveOrZero(l.tail)
.
We can then immediately rewrite our equation into !(l.head < 0) && allPositiveOrZero(l.tail)
.
What’s the next step?
Step 2
Going one step further, identity (2) now also applies to !(l.head < 0)
, so we can further rewrite the function into
def allPositiveOrZero2(l: IntList): Boolean =
if !l.isEmpty then
l.head >= 0 && allPositiveOrZero2(l.tail)
else true
equational.worksheet.sc
Can you simplify it further, eliminating the remaining if
expression?
Step 3
Identity (5) can be written as if c then e else true
= !c || e
.
Substituting c
with !l.isEmpty
and e
with (l.head >= 0 && allPositiveOrZero(l.tail))
(notice the ()
! One should always be careful about substitutions,
in order to not introduce some confusion between operators,
just like in algebra), and we get
!!l.isEmpty || (l.head >= 0 && allPositiveOrZero(l.tail))
.
One more step?
Step 4
We can apply (1) as well, resulting in our final code
def allPositiveOrZero3(l: IntList): Boolean =
l.isEmpty || (l.head >= 0 && allPositiveOrZero3(l.tail))
equational.worksheet.sc
Pretty short and sweet! And think of what this code says… it’s exactly the English spec! “A list is all positive or zero if either it is empty, or its first element is positive or zero, and all remaining elements are all positive or zero.”
A note on side effects
A recurring question in the find
lab was: why does my code print too much, or too little?
To explore this, here is a puzzle. Can you find an input for which the following two programs behave differently?
def allPositiveOrZeroPrint(l: IntList): Boolean =
l.isEmpty || {
val headPositive = l.head >= 0
val tailPositive =
println("checking tail!")
allPositiveOrZeroPrint(l.tail)
tailPositive && headPositive
}
equational.worksheet.sc
def allPositiveOrZeroPrintInlined(l: IntList): Boolean =
l.isEmpty || {
l.head >= 0 && {
println("checking tail!")
allPositiveOrZeroPrintInlined(l.tail)
}
}
equational.worksheet.sc
Solution
Due to the short-circuiting behavior of the ||
operator, the first program will print “checking tail” when l.head
is negative, whereas the second one will not.
Bonus question: what would happen if the val
s were replaced by def
s in the first program?
When rewriting code using equational rewriting, mind what assumptions you’re making about the code! In many cases, equations are valid only if you only care exclusively about the value of the expression, and not what other “side effects” it does during the computation, such as println
ing to the screen, updating var
s, etc. We will say much more about side effects in week 10.