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

Tips

Here are a few tips to help you bring your lab submission to a success:

Bugs in the wild

These stories illustrate debugging real-world problems, and the interplay between software engineering, systems engineering and process design:

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