Last updated on

Good to understand (week 3)

This Good to understand document provides answers to commonly asked questions.

This document does not contain new material. We encourage you to check out the table of contents and choose the sections that interest you.

Other than that, you are not required to read the entire document, as its content is already covered in the lectures, exercises or labs.

Sections marked with 📘 are alternative explanations of course material that is already covered in lectures, exercises or labs. Sections marked with 🚀 are here to satisfy your curiosity, but you will not be graded on them.

Useful pattern matching syntax 🚀

Wildcard patterns

You can use _ to discard a part of the pattern. E.g.

case Cons(_, Cons(_, tail)) =>
  // `_` doesn't capture any value

Given a following function matchHead:

def matchHead(list: IntList, target: Int) =
  list match
    case IntCons(head, tail) =>
      if head == target then print(s"Head matches: $target :)")
      else print(s"Head doesn't match :(")
    case IntNil => print(s"Head doesn't match :(")

2025-09-28/code/useful-pattern-matching-sytax.worksheet.sc

Can you rewrite it so it uses wildcard patterns?

Answer
def matchHead(list: IntList, target: Int) =
  list match
    case IntCons(head, _) =>
      if head == target then print(s"Head matches: $target :)")
      else print(s"Head doesn't match :(")
    case _ => print(s"Head doesn't match :(")

2025-09-28/code/useful-pattern-matching-sytax.worksheet.sc

If guards

Scala lets you add extra conditions in case branches by using a “pattern guard”, written case pattern if guard. Both the pattern and the guard need to be satisfied to enter the branch. For example:

case IntCons(head, _) if head >= 0 =>
  ??? // only lists starting with non-negative number fall here
case _ =>
  ??? // all else falls hare

Can you rewrite matchHead above so it takes advantage of if guards?

Answer
def matchHead(list: IntList, target: Int) =
  list match
    case IntCons(head, _) if head == target =>
      print(s"Head matches: $target :)")
    case _ => print(s"Head doesn't match :(")

2025-09-28/code/useful-pattern-matching-sytax.worksheet.sc

Checking for equality

You can also check for equality using pattern matching directly. To do so, either include the target of the comparison in backticks, or use a name that starts with a capital letter.

val apple = "I have an apple"
val Bananas = "I have some bananas"

x match // x has type String here
  case `apple` => // matches if x == "I have an apple"
  case Bananas => // matches if x == "I have some bananas"
  case apple =>
    // matches any x
    // this will also define a new variable target with value equal to x that will shadow val apple = "I have an apple"

Can you rewrite matchHead above so it takes advantage of equality checks?

Answer
def matchHead(list: IntList, target: Int) =
  list match
    case IntCons(`target`, _) => print(s"Head matches: $target :)")
    case _ => print(s"Head doesn't match :(")

2025-09-28/code/useful-pattern-matching-sytax.worksheet.sc

or

def matchHead(list: IntList, Target: Int) =
  list match
    case IntCons(Target, _) => print(s"Head matches: $Target :)")
    case _ => print(s"Head doesn't match :(")

2025-09-28/code/useful-pattern-matching-sytax.worksheet.sc

The second version is not recommended, because Target isn’t a global constant: this violates the Scala convention that variable names start with a lower-case letter. In contrast, names beginning with an upper-case are reserved for types, type parameters, objects, and constants. The only typical use of this feature (matching against the value of an identifier that starts with a capital letter) is constants.

Using val/var on class parameter fields. 📘

If nothing is put on a parameter of a non-case-class, the parameter is are just treated like private val to the class: you cannot access it from the outside of the class, and the class itself cannot modify it. In the case where it is not used within any methods, Scala can compile it away, not storing the value inside the class at all.

class A(x: Int):
  val n = x + 1
  
val a = A(5)
a.n // 6
a.x // won't compile

If we put var/val, then the parameters are also treated as public variables/values, so we can read (and write into, in the case of var) them from both inside and outside the class.

class A(var x: Int, val y: Int):
  val n = x + y
  
val a = A(5, 6)
a.n // 11
a.y // 6
a.x = 1 // ok 

We can also put private/protected before a var/val, and it should behave as expected.

For case classes, however, it is a bit different: all parameters are set to be public vals by default. You can still override this behavior, the same as for non-case-class.

However, making a constructor parameter of a case class private wouldn’t be safe anyway. Do you see why?

Spoilers!

Even though, we can’t access our private val directly from the outside, we can still extract it using pattern matching.

case class A(private val secret: String)
  
val a = A("secret")
a.secret // won't compile

a match
  case A(secret) => secret // ok

In general, we should have case class parameters stay as public vals, so it agrees with our concept of them being just a named aggregation of the fields it is made of. There are no such restrictions on non-case classes.