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
- Holiday: Monday 21 September is a public holiday!
- Troubleshooting: people encountered various problems when setting up the tools for this course. There is a troubleshooting guide which contains help for common issues. If you don’t find what you’re looking for there, feel free to reach out on Ed!
- If, for whatever reason, your local set-up isn’t working properly yet and you’d like to get started on exercises/labs in the meantime, you can always make use of our virtual machines. Besides being ready to use, the VM also provides you an opportunity to familiarise yourself with examination conditions.
- Course policies: for questions about the course schedule, grading and examination, please consult the policies and schedule and computer exam logistics first. Of course we are more than happy to help you out if your question isn’t covered there.
- Moodle reminders: Moodle erroneously sent reminders about lab submissions being overdue. We’re looking for a solution to turn these off, but in the meantime you can safely ignore them if you have succesfully submitted your lab before the deadline.
Interesting links and Ed questions
- What are “strong types”?
- What is the difference between various
sbtcommands? and The Book of sbt - We have posted a detailed git transcript to help you study. It includes an example of patch preparation and detailed demos of advanced history browsing (including blaming and region history).
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:
- Currying VS not currying
- Named VS anonymous
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
- It will be called from many different places, or
- The functionality is complex enough to require a name
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