Last updated on

Week 1/2: Setup, Recursion and Higher-order Functions

Congratulations on completing your first and 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

Interesting links and Ed questions

To curry or not to curry? The many ways to define a function

With higher-order-functions, we have seen lots of different ways to define functions. You now have two dimensions of choice:

There is also the option of defining your function as a val or a def.

Named VS Anonymous

Whether you name your function or not is a matter of succinctness.

Naming a function is advantageous when

Often, we have very simple functionality that doesn’t require a name. The two following blocks of code show the same function written in an anonymous and named way respectively. Would you prefer reading this:

studentResults.filter(_.grade > 5)

Or this?

def hasGradeGreatedThan5(s: StudentResult) =
  s.grade > 5
studentResults.filter(hasGradeGreaterThan5)

The second one is much longer and hence much more distracting to read.

In principle, you should use anonymous functions for small functions that are meant for a very specific and simple task that you are using in a single place. It makes the code smoother to read.

Currying or not currying, that is the question

Currying a function means transforming a function that takes multiple arguments to multiple functions that each take part of the arguments. For example, the following function:

def add(x: Int, y: Int): Int = x + y

can be curried this way:

def addCurried(x: Int)(y: Int): Int = x + y

This is useful when you want to create partially applied functions.

For example, we can use addCurried to create a function that always add 5 to its parameter:

val add5 = addCurried(5) // 'add5' is a function of type (Int) => Int
println(add5(3)) // prints 8

Suppose you want to implement logging in your program. To do this, you implement this method:

def log(component: String, message: String): Unit =
  println(s"[$component] $message")

Each part of your program uses a different value for component, so that you know exactly from where each log message comes from.

But you end up with a lot of duplicated code. Within one part of your code, the component value is the same for all log calls. Isn’t there a way to simplify this? Yes, currying!

You redefine your function as:

def logCurried(component: String)(message: String): Unit =
  println(s"[$component] $message")

Then, in each part of your code, you create a logger that only gets used there. For example, if you have a server component:

val logger = logCurried("SERVER")

// Later in the server code
logger("Received request")

This is what we mean when we say partially applied functions, and this is where currying shines.

We’ll see more examples of this throughout the course.

val or def

Functions can be defined using either val or def, so which one should you use? For example, when defining a curried version of addition, is it better to write this def?

def add(x: Int)(y: Int): Int = x + y

2025-09-21/code/ReturningAFunction.worksheet.sc

… or this val?

val add = (x: Int) => (y: Int) => x + y

2025-09-21/code/ReturningAFunction.worksheet.sc

In practice, def is the most commonly used: use it by default. When the function is needed repeatedly as a value, use val. def can also be used, but will take a small amount of time to translate to a value every time it is used as one, thus val can provide slightly better performance in such cases. Thus, a good rule of thumb is to use def when defining functions that will mostly be called directly, and val for functions that will mostly be passed to other functions.

val numbers = List(1, 2, 3, 4, 5)

def add1(x: Int, y: Int): Int = x + y

object Math:
  def add2(x: Int, y: Int): Int = x + y

@main def run() =
  def internalAdd(x: Int, y: Int): Int = x + y

  val add3: (Int, Int) => Int = (x: Int, y: Int)=> x + y

  // All of these can be used in the same way
  println(numbers.foldLeft(0)(add1))
  println(numbers.foldLeft(0)(Math.add2))
  println(numbers.foldLeft(0)(internalAdd))
  println(numbers.foldLeft(0)(add3))

  // val has slightly better performance if used repeatedly as a value
  val numbers2 = List(6, 7, 8, 9, 10)
  val numbers3 = List(11, 12, 13, 14, 15)
  println(numbers.foldLeft(0)(add3))
  println(numbers2.foldLeft(0)(add3))
  println(numbers3.foldLeft(0)(add3))

There is one case in which def versus val makes a major difference, however: when the definition itself has side effects. val is evaluated immediately; def is delayed until it is called.

For example, here are four code fragments. Can you say what each of them prints?

val add1v =
  println("Oh! A val!");
  (x: Int) => x + 1

2025-09-21/code/ValDef.worksheet.sc

def add1d =
  println("Oh, a `def`!");
  (x: Int) => x + 1

2025-09-21/code/ValDef.worksheet.sc

add1v(2)

2025-09-21/code/ValDef.worksheet.sc

add1d(2)

2025-09-21/code/ValDef.worksheet.sc

Spoilers!
val add1v =
  println("Oh! A val!");
  (x: Int) => x + 1
// Prints "Oh! A val!"

2025-09-21/code/ValDef.worksheet.sc

def add1d =
  println("Oh, a `def`!");
  (x: Int) => x + 1
// Does not print anything

2025-09-21/code/ValDef.worksheet.sc

add1v(2)
// Returns 3, does not print anything

2025-09-21/code/ValDef.worksheet.sc

add1d(2)
// Prints "Oh, a `def`!", then returns 3

2025-09-21/code/ValDef.worksheet.sc