Last updated on
Week 3: Decomposition and Pattern Matching
Congratulations on completing your third week of CS-214! Here is a round-up of interesting questions, tips for exercises and labs, and general notes about the course.
Administrivia
- Help session rooms: with most people being in the larger help session rooms, queueing times for asking a lab assistant for help were sub-optimal. For quicker help with exercises and a quieter room, please join our assistants in rooms INM 201 and INF 019.
- Callbacks are graded: a gentle reminder that callbacks are graded and submitted like the labs. See the course policies for more information.
Tips
Here are a few tips to help you bring your lab submission to a success:
- Worksheets: they are helpful to quickly test and iterate on components of your code.
- Testing: it becomes easier when you write modular code, in particular when classes have a single responsibility. If you find that it’s difficult to test the behaviour of your component, it might be a signal that your code isn’t modular enough.
- Enumeration: to use a value of an enumeration without having to specify the enumeration name, you can import the name (e.g.
import IntTree.*). See the Scala reference.
Bugs in the wild
These stories illustrate debugging real-world problems, and the interplay between software engineering, systems engineering and process design:
- Boids in space: the Ariane 5 software failure
- Speed of light is a first-world problem: the 500-mile email barrier test
Being lazy pays off
Sometimes – don’t take this face-value as career advice
There was a good question on Ed about avoiding the evaluation of arguments when they’re not needed for computation: call by name in lambda expression types. The answer proposes a design that can make a higher-order function accept call-by-name arguments, and as such avoid unnecessary computations. We will cover this in more detail in week 7, but let’s have a peek at its mechanics!
Here’s the definition of associative as mentioned in the Ed question, adapted to use pattern matching:
def associative(f : (Int, Int) => Int, base : Int) : IntList => Int =
l => l match
case IntNil() => base
case IntCons(n, ns) => f(n, associative(f, base)(ns))
2026-09-25/lazy.worksheet.sc
Can you reimplement associative lazily using call-by-name arguments, so that we don’t access the values of a list to compute it length? i.e. what Rémy suggested in his answer on Ed.
Solution
def associativeLazy(f : ( => Int, => Int) => Int, base : Int) : IntList => Int =
l => l match
case IntNil() => base
case IntCons(n, ns) => f(n, associativeLazy(f, base)(ns))
2026-09-25/lazy.worksheet.sc
Can you define product such that it short-circuits if it encounters a 0?
Solution
val product2 : IntList => Int = {
def multiply (x: => Int, y: => Int) = if x == 0 then 0 else x * y
associativeLazy(multiply, 1)
}
2026-09-25/lazy.worksheet.sc
Tip: don’t take my word for it, but validate it yourself! See what happens for different inputs, and prepend println(s"$x"); to the body of multiply
Note: => Int is not a valid type, so we can’t write an anonymous function (x: => Int, y: => Int) => x * y. It’s special syntax for method parameters. Hence why we defined the function in the snippet above.
Note: this is a valid optimisation because 0 is an annihilator for the _*_ operation. Similarly, we can apply such an optimisation for false with _&&_, and true with _||_.
Recall takeWhilePositive from the recursion exercises from week 1.
def takeWhilePositive(l : IntList): IntList =
l match
case IntNil() => IntNil()
case IntCons(n, ns) => if n > 0 then IntCons(n, takeWhilePositive(ns)) else IntNil()
2026-09-25/lazy.worksheet.sc
This has the neat property that it short-circuits when it encounters a non-positive number, and as such skips iterating over the tail of a list if possible.
Now, if you were to implement this using foldRight, it might look as follows:
def foldRight(l: IntList, base: IntList, acc: (Int, IntList) => IntList): IntList =
if l.isEmpty then base
else acc(l.head, foldRight(l.tail, base, acc))
def takeWhilePositiveFold(l : IntList): IntList =
foldRight(l, IntNil(), (n, acc) => if n > 0 then IntCons(n, acc) else IntNil())
2026-09-25/lazy.worksheet.sc
However, this has the downside that we will unnecessarily traverse the tail of a list beyond the first non-positive element. Can you amend these two functions to avoid that?
Solution
def foldRightLazy(l: IntList, base: IntList, acc: ( => Int, => IntList) => IntList): IntList =
if l.isEmpty then base
else acc(l.head, foldRightLazy(l.tail, base, acc))
def takeWhilePositiveLazy(l : IntList): IntList =
foldRightLazy(l, IntNil(), (n, acc) => if n > 0 then IntCons(n, acc) else IntNil())
2026-09-25/lazy.worksheet.sc
Code-quality improvements based on your lab submissions
Here are a few common code smells and code style improvements, based on your lab submissions:
Boolean literals in ifs
When a result of an if expression is a Boolean with one branch as a literal (true or false) you can usually replace the if with a logical operator. Mastering Truth tables for the most common logical operators is very useful in programming.
Before:
val entryChildren =
if entry.isDirectory() && entry.hasChildren() then
findByNameAndPrint(entry.firstChild(), name)
else false
2026-09-25/lazy.worksheet.sc
After:
val entryChildren =
entry.isDirectory() && entry.hasChildren() && findByNameAndPrint(entry.firstChild(),name)
2026-09-25/lazy.worksheet.sc
Unnecessary result of an if branch
Whenever you are writing an if in a statement position there is no need for every branch to return an explicit value. A side-effecting if doesn’t even need both branches.
Before:
if !entry.isDirectory() && entry.size() >= minSize then
println(entry.path())
true
else
false
2026-09-25/lazy.worksheet.sc
After:
if !entry.isDirectory() && entry.size() >= minSize then
println(entry.path())
2026-09-25/lazy.worksheet.sc
Nested side-effecting ifs
Instead of writing two nested if statements that will produce a side-effect, you can merge them together into one condition.
Before:
if entry.isDirectory() then
if entry.hasChildren() then
findAllAndPrint(entry.firstChild())
2026-09-25/lazy.worksheet.sc
After:
if entry.isDirectory() && entry.hasChildren() then
findAllAndPrint(entry.firstChild())
2026-09-25/lazy.worksheet.sc
return in a return position
In Scala, the last expression in a function body will be returned by default, no need to explicitly return it.
Before:
def findAllAndPrint(entry: Entry): Boolean =
// ...
return true
2026-09-25/lazy.worksheet.sc
After:
def findAllAndPrint(entry: Entry): Boolean =
// ...
true
2026-09-25/lazy.worksheet.sc