Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.
Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit.
Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 1.8: Functions, Types, and Errors

More on Function Calls and the Call Stack
Take a look at the program code below. It features a custom function greatestDistance
that takes in the x and y coordinates of three points and determines the longest distance
between any two of those points. The function uses of another custom function, distance
,
which saw in the previous chapter; it calculates the distance between two given points.
def distance(x1: Double, y1: Double, x2: Double, y2: Double) = hypot(x2 - x1, y2 - y1)
def greatestDistance(x1: Double, y1: Double, x2: Double, y2: Double, x3: Double, y3: Double) =
val first = distance(x1, y1, x2, y2)
val second = distance(x1, y1, x3, y3)
val third = distance(x2, y2, x3, y3)
max(max(first, second), third)
There are two senses in which a function can be “inside” another:
Also, you may call a function within the body of another
function. Here, greatestDistance
calls distance
(three times).
The following animation elaborates.
Before Our Next Coding Session, Here’s a Summarizing Exercise on Functions
I like it that the animations show how the computer “thinks”. I mean, it doesn’t really think but just mechanically follows each function, starting with the inner ones and proceeding outwards. It’s easier to read code when you can think like the computer.
As we move towards programs that are more complex and more delightful, it’s important that you learn to make reliable inferences about how a given piece of code works when it runs. Or about a piece of your own code that fails to work right.
Study the following program. It doesn’t accomplish anything useful as such, but it serves as an exercise in function calls, return values, and printing.
Programming Exercise: leaguePoints
and teamStats
In this assignment, you’ll create two functions, using one as a building block for the other.
Programming is like building with smart Lego that you design yourself!
—origin unknown
Task description
In the file week1.scala
, write two effect-free functions that compute a sports team’s
league points, given the team’s results.
The first of the functions should match this specification:
Its name is
leaguePoints
.As parameters, it expects the number of wins and the number of draws that the team has played. In that order, as integers.
It returns — but does not print! — the team’s total league points as an integer. A win is worth three points and a draw is worth one point (and a loss is worth no points at all).
The second function should match this specification:
Its name is
teamStats
.As parameters it expects, in order, the name of the team (a string) and the numbers of wins, draws, and losses (integers).
It returns (but does not print!) a string in the format "Name: X/N wins, Y/N draws, Z/N losses, P points". For instance, the call
teamStats("Liverpool FC", 8, 7, 7)
should return the string "Liverpool FC: 8/22 wins, 7/22 draws, 7/22 losses, 31 points"
Workflow
First, create the function leaguePoints
. Follow the workflow suggested in Chapter 1.7’s
programming assignments. Don’t forget to test that your function works before you continue.
Then, in the same manner, write and test the function teamStats
. Here are a few additional
tips:
When writing a function body with multiple commands, remember to indent it.
To get the team’s points total, call the
leaguePoints
function you created earlier.Your code will be more readable if you use a local variable to store the total number of games played by the team.
Submit your solution only when you have written and tested both functions.
A+ presents the exercise submission form here.
More Exercises
Additional practice: Feet and inches, the other way around
This exercise continues the imperial units theme from Chapter 1.7. You’ll create three one-liner functions, the first of which will be useful for implementing the other two.
First, write an effect-free function toInches
that takes a length in
meters and returns the equivalent number of inches. An inch is 2.54 cm.
Here are some examples:
toInches(1.8)res4: Double = 70.86614173228347 toInches(0.0254)res5: Double = 1.0
Then create two functions that you can use in combination to convert a given number of meters into whole feet and leftover inches. For instance, 1.8 meters is five feet and about eleven inches. Here’s how the two functions should work:
wholeFeet(1.8)res6: Double = 5.0 remainingInches(1.8)res7: Double = 10.866141732283475
Make use of toInches
when you implement these functions. Revisit
Chapter 1.6 to review the tools available in package scala.math
.
Enter all three functions in week1.scala
.
A+ presents the exercise submission form here.
A challenge version of see-above
Find out on your own how to use pairs (pari) in Scala. Apply what you learned to solve the previous problem in a new way.
Create toInches
as suggested above. Then implement a function
toFeetAndInches
that combines the functionality of wholeFeet
and remainingInches
. The function should return the numbers
that represent feet and inches as a pair. Like so:
toFeetAndInches(1.8)res8: (Double, Double) = (5.0,10.866141732283467) toFeetAndInches(0.0254)res9: (Double, Double) = (0.0,1.0)
(We’ll have more to say about pairs in Chapter 9.2.)
A+ presents the exercise submission form here.
Programming Exercise: verbalEvaluation
Let’s get back to course grades and create an effect-free function that produces a verbal assessment of a student’s work in our imaginary example course. Here’s a template for the function:
def verbalEvaluation(projectGrade: Int, examBonus: Int, participationBonus: Int)
val descriptions = Buffer("failed", "acceptable", "satisfactory", "good", "very good", "excellent")
// PLEASE FILL OUT THIS PART OF THE SOLUTION. YOU CAN REMOVE THIS COMMENT.
The function should work as follows:
The three parameters represent a project grade and bonuses for an exam and participation, exactly as in
overallGrade
from Chapter 1.7.Instead of an integer, this function should return a string description that matches the student’s overall grade. For instance, a grade of two is described as "satisfactory" and a grade of five as "excellent".
Implement the function properly:
Copy the above code template into
week1.scala
.There is a small but serious error in the given template code! You’ll need to fix it.
IntelliJ’s description of the error isn’t too fantastic, for a reason that we’ll discuss at the end of this chapter. But the fix you need to apply is very simple.
Fill in the function body. Use the buffer that’s already defined for you, along with the
overallGrade
function from Chapter 1.7. Once you combine these two, not much code is needed.
A+ presents the exercise submission form here.
Programming Exercise: doubleScore
The next function is effectful: it modifies the contents of a given buffer. (In this
respect, it is similar to Chapter 1.6’s removeNegatives
.)
Task description
Let’s consider an imaginary game where multiple players compete and collect points.
As the game progresses, a player’s score may occasionally double, but players may
also suffer losses. In this assignment, you’ll create an effectful function in
week1.scala
that doubles a given player’s score. The function should work as
follows:
Its name is
doubleScore
.As its first parameter, it receives a reference to a
Buffer
whose elements are integers that represent the current scores of each player.As its second parameter, it receives an integer that determines which player’s score should be doubled: one means the first player’s score, two the second player’s, and so forth.
It modifies the contents of the given buffer so that the targeted player’s score becomes twice what it was before.
Here’s an example scenario:
val scoresOfEachPlayer = Buffer(2, 10, 5, 2)scoresOfEachPlayer: Buffer[Int] = ArrayBuffer(2, 10, 5, 2) doubleScore(scoresOfEachPlayer, 3)doubleScore(scoresOfEachPlayer, 4)scoresOfEachPlayerres10: Buffer[Int] = ArrayBuffer(2, 10, 10, 4)
Instructions and hints
You’ll need to indicate that the first parameter is a buffer whose elements are integers. Use square brackets around the buffer’s type parameter as in Chapter 1.5.
The second parameter identifies the target player. It uses a one-based numbering scheme, whereas buffers’ indices start from zero (Chapter 1.5). You’ll need to take this into account.
Don’t worry about special cases such as what happens if someone passes in a player number that’s too large or too small. O1’s programming assignments will tell you explicitly when you’re expected to handle invalid inputs.
You may assume that each player’s score is a positive integer.
The function doesn’t need to return anything.
A single assignment command that targets the right index in the buffer will do for a function body.
A+ presents the exercise submission form here.
Programming Exercise: penalize
Let’s stay with the same imaginary game. You’ll now create an effectful function that reduces a player’s score. It should work like this:
Its name is
penalize
.As in the previous function, the first parameter refers to a buffer that contains the players’ scores and the second parameter is the number of the target player.
The third parameter indicates the size of the penalty: how many points should be subtracted from the target player’s score. You can assume that this is a positive number.
However, the rules of the game dictate that a player’s score can never drop to zero or below; a player will always have at least a single point. If a player receives a penalty that would violate this rule, the player’s score will only be reduced down to one.
The function returns an integer: how many points were actually removed from the target player.
Here’s a usage scenario:
val scoresOfEachPlayer = Buffer(2, 10, 5, 2)scoresOfEachPlayer: Buffer[Int] = ArrayBuffer(2, 10, 5, 2) penalize(scoresOfEachPlayer, 2, 3)res11: Int = 3
We subtract three from the player number two’s score. The full penalty could be applied, and the return value indicates that three points were successfully removed.
A look at the scores buffer confirms that the reduction happened. The second score has dropped to seven:
scoresOfEachPlayerres12: Buffer[Int] = ArrayBuffer(2, 7, 5, 2)
Let’s give the same player a twelve-point penalty:
penalize(scoresOfEachPlayer, 2, 12)res13: Int = 6 scoresOfEachPlayerres14: Buffer[Int] = ArrayBuffer(2, 1, 5, 2)
However, only six points were successfully removed...
... because a player must always have at least one point.
If it seems clear to you how to solve this assignment, feel free to go ahead and write the entire solution right now. Otherwise, we recommend approaching it in two stages:
Stage 1 of 2: don’t worry about the return value
For now, just write a version of the function that reduces the target player’s score in the buffer. Don’t concern yourself, yet, with whether the function returns the right value.
You can use Scala’s library function min
(Chapter 1.6) to determine how many points
can be removed. Another approach is to use max
. There are other ways to make the function
work, too, and you’re free to use any of them.
Test your function to ensure that it works!
Stage 2 of 2: sort out the return value
You already know that in Scala, a function return value is determined by the last command that is executed as part of the function call. A beginner’s first sketch for this algorithm might thus look something like this:
Reduce the player’s score, but not below one.
Calculate the number of points that were removed and return that result.
The problem is that to compute the return value, we need both the third parameter (the size of the penalty) and the player’s original score. But after we’ve applied the reduction, the original score is no longer stored anywhere and there’s no way we can compute the return value at Step 2.
Another attempt:
Calculate the number of points that can be removed and store that result.
Reduce the player’s score by that amount.
Return the amount stored at Step 1.
This version is better because it computes the future return value before applying the penalty.
To implement this improved algorithm, you’ll need to store the actual size of the penalty at Step 1. That’s well within your grasp: use a local variable.
Hints
Bear in mind that a multi-line function returns the value of the expression that was evaluated last. Such an expression can be simply the name of a variable.
If you want, you can view the following animation. It presents one way to solve this assignment. (The animation does not show the program code; that’s something you’ll have to write yourself.)
There is something worth mentioning in the animation apart from the solution itself.
It illustrates that penalize
receives a reference to a buffer, not a copy the buffer
with identical but separate contents. This is why we can observe a change in the buffer
through another reference that points to the same buffer.
A+ presents the exercise submission form here.
Functions with No Parameters
All the functions we’ve discussed so far have taken at least one parameter. It’s also possible to define a function that takes no parameters.
def onePlusOne = 1 + 1
No round brackets, no parameter list.
That function always returns the same number:
onePlusOneres15: Int = 2 onePlusOneres16: Int = 2
Different Kinds of Errors
Ninety percent of your time will be spent searching for errors in the ten percent of code that you last wrote.
—origin unknown
Finding and fixing errors is an essential activity in programming and takes up a large chunk of programmers’ time. Now that you have your hands dirty writing program code, it’s good to learn the main types of errors.
Errors at compile time
Compilation-time errors (käännösaikainen virhe) can be detected automatically even before the program is run. The name refers to how these errors can be spotted by auxiliary programs called compilers. (Compilers convert your program code into a form that is more readily executable by a computer. More on that in Chapter 5.4.)
Compile-time error messages result from mistakes such as incorrect punctuation and (in Scala) attempting to assign a value of an incompatible type to a variable. Many compile-time errors are syntax errors (syntaksivirhe): they indicate violations of the programming language’s syntactical rules (grammar).
For the experienced programmer, most compilation-time errors are quick and easy to fix. For the beginner, too, this tends to be the least problematic category of errors.
In Chapter 1.7, we discussed how IntelliJ highlights some errors in the editor and displays error messages in the Build tab. All of those are examples of compilation-time errors (even though IntelliJ red-alerts many of them instantly in the editor already before full compilation).
Errors at runtime
Runtime errors (ajonaikainen virhe) are more irksome: they only reveal themselves when the program is being executed and may not show up for all input values.
The classic example of a runtime error is division by zero: if it transpires that we’ve used an expression where the denominator evaluates to zero, a runtime error occurs. The error will “crash” the program (i.e., abruptly abort the program’s execution) unless we’ve specifically prepared for that contingency.
Indexing errors are another example. You saw some of them in Chapter 1.5 when trying to use too-large or too-small integers to access a buffer’s elements.
Exception (poikkeus) is an alternative name for some runtime errors.
We’ll return to runtime errors and program crashes in Chapter 4.1.
Note that in the REPL, the distinction between compile-time and runtime errors is blurry, because our code is (first) compiled and (then) run as soon as we type it in.
Logical errors
A logical error (looginen virhe) is what we call it when our program “works” in the technical sense — it runs without crashing — but does something other than what was intended. For example, using the wrong arithmetic operation is a logical error.
Some logical errors are easy to spot by looking at the code or observing what the program does. Others are much harder. In any case, it’s up to the programmer to find them, as these errors don’t produce error messages.
On Data Types and Scala
Let’s round off Week 1 with a few observations on how data types are used in Scala programs and how this has already manifested itself in the code that we’ve written. The gray-bordered box below sets up the topic but isn’t strictly necessary for our purposes in O1. Feel free to skip it if you’re in a hurry. The text that follows the box is more crucial.
On Type Systems
The nature of a programming language is strongly shaped by its type system (tyyppijärjestelmä): the general rules that govern data types of program components and how those types affect the way people program.
The details of type systems aren’t part of O1, nor is the theory behind them, but we’ll take an educational dip into some basic concepts.
Type safety
Scala is a very type safe (tyyppiturvallinen) language. Each value in a Scala program has a specific data type, and that type determines which operations we can apply to the value. Integers can be used in arithmetic, strings can be concatenated, and buffers can receive new elements; on the other hand, an integer can’t store elements, and an attempt to do such a thing brings a timely error message.
The classic example of a programming language with an unsafe type system is the C language. In C, the programmer can write code that “goes against the types”, leading to behavior that depends on context and can be, in some cases, unpredictable. An unsafe type system allows alternative ways of solving problems but increases the likelihood of errors — errors that can slow down even accomplished programmers and damage the final product.
Static vs. dynamic typing
In Chapter 1.2, we noted that programs have both a static and a dynamic nature, a fact that is also highlighted by this ebook’s animations.
The same duality is also reflected in how programming languages handle types.
Scala is statically typed (staattisesti tyypitetty): each part of a Scala program has a type that is well-defined already in the program’s static form, the program code. For instance, each variable and expression has a specific data type that can be determined by looking at the code.
Among other things, this means that our tools can warn us about invalid code — like attempting to pass an integer as a parameter where a string is required — even before we run our program.
(Using a value with the wrong type — say, as a function parameter — might sound like a silly mistake. However, such mistakes are far more common and understandable than you might think, even in code written by professional programmers. You’ll notice this yourself sooner or later.)
Static typing can also lead to more efficient (faster) programs support better programming tools. These benefits are especially prominent in larger programs that aim for high quality and reliability.
In a dynamically typed (dynaamisesti tyypitetty) language, the parts of program code don’t have types as such. For instance, in the (most commonly used form of the) Python programming language, variables don’t have types, and you can assign any kind of value to any variable. Exactly what type of value gets assigned to a particular variable may be determined by events that happen during a particular program run and influenced by user input.
Dynamic typing is more flexible in some respects and makes some aspects of programming convenient; a dynamic typed language may also be simpler than a statically typed one. The downsides of dynamic typing include an increased risk of programming errors and the related problem that many errors cannot be spotted without running the program on various inputs.
Programmers disagree, sometimes vehemently, about the relative merits of static and dynamic typing. The topic has been the cause of many a religious war, civilized conversation, and minor dispute.
Type annotations
In many statically typed languages (such as Java), the programmer writes type annotations all over program code. In these languages, when you define a variable, you’ll always (or usually) also write down the variable’s data type. The type of most or all return values must also be explicitly stated.
On the other hand, in dynamically typed languages such as (regular) Python, such type annotations don’t feature at all.
Scala is statically typed, but we haven’t written many type annotations in our programs. It was only recently, in Chapter 1.7, that we first paid proper attention to the matter, when we introduced the need to annotate each parameter variable with a type. We’ve mostly been able to ignore type annotations so far, because Scala can automatically infer much of the static type information inherent in a program. As a result, Scala often lets us write code with the same convenience that’s usually associated with dynamic typing while providing the guarantees that static typing affords.
Type inference in Scala
There are certain parts of a Scala program where type annotations are required. The parameter variables of functions are one such part: we follow each parameter’s name with a colon and a data type.
It’s not just parameters that have data types in Scala, but the types of other constructs can usually be determined automatically by Scala’s type inference (tyyppipäättely).
For example, we have defined variables like this:
val number = 123
val text = "The number is " + number + "."
Those commands are in fact shortened versions of these type-annotated ones:
val number: Int = 123
val text: String = "The number is " + number + "."
The shorter versions work, because the types of the variables can be readily inferred from the values we assign to them. It’s perfectly legal to write the type annotations — as we did right there — but it’s unnecessary, and we don’t usually bother.
A familiar function definition appears below. In this piece of code, too, we’ve actually omitted a data type and left it for the computer to determine:
def average(first: Double, second: Double) = (first + second) / 2
The above is short for this:
def average(first: Double, second: Double): Double = (first + second) / 2
The final Double
annotation after the parameter list indicates the type of the return
value: this function takes in two decimal numbers and also returns a decimal number.
However, the return type can be automatically inferred from the expression (first +
second) / 2
, given that the parameters’ types are known. So we can skip writing that
bit if we want.
Later in O1, we’ll come across some other circumstances where it’s necessary to write explicit type annotations in Scala code.
About that earlier error
Remember the error in the given template for verbalEvalution
? This one:
def verbalEvaluation(projectGrade: Int, examBonus: Int, participationBonus: Int)
val descriptions = Buffer("failed", "acceptable", "satisfactory", "good", "very good", "excellent")
// ...
IntelliJ’s editor highlights the end of the def
line at the
top. The error message opines: Missing return type.
Yeah, well, it’s true that you could write a return type there, too, but the actual problem is the missing equals sign.
The more general lesson here is this: although the computer can spot certain errors for us, its decriptions of the problem and what to do about it aren’t always apt. Some of the suggestions you’ll see in error messages do hit the mark, but ultimately, it’s the programmer’s responsibility to analyze the situation and apply the right fix.
Summary of Key Points
A function can call another function.
When that happens, a new frame is added to the top of the call stack. The calling function’s execution is suspended while it waits for the other function to finish. Only the top frame of the call stack is actively used at any given time.
You can write a function that performs a subtask and use it within another custom function.
Programs can have compilation-time errors, runtime errors, and logical errors.
Scala tools can automatically infer the types of many program components automatically. However, there are a few places in Scala programs where type annotations are required; parameter variables are the most obvious example.
Links to the glossary: function, function call, call stack, frame, local variable; error; type annotation, type inference.
What Now and What Next?
You can now write your own program components — functions — constructing them from assorted materials such as numbers, buffers, and other functions. But we’re still a bit of ways from creating an entire program with multiple components that work in unison.
There are a number of approaches to building a larger program. We’ll adopt one good approach in Week 2.
On Academic Integrity and AI Tools
Feedback
Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.
Credits
Thousands of students have given feedback and so contributed to this ebook’s design. Thank you!
The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.
The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.
The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Kai Bukharenko, Nikolas Drosdek, Kaisa Ek, Rasmus Fyhrqvist, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Kaappo Raivio, Timi Seppälä, Teemu Sirkiä, Onni Tammi, Joel Toppinen, Anna Valldeoriola Cardó, and Aleksi Vartiainen.
The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.
The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.
The other diagrams and interactive presentations in the ebook are by Juha Sorva.
The O1Library software has been developed by Aleksi Lukkarinen, Juha Sorva, and Jaakko Nakaza. Several of its key components are built upon Aleksi’s SMCL library.
The pedagogy of using O1Library for simple graphical programming (such as Pic
) is
inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and
Krishnamurthi and Picturing Programs by Stephen Bloch.
The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services; dozens of Aalto students and others have also contributed.
The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It has been designed and implemented by various students in collaboration with O1’s teachers.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits for this page
The idea for the question on dynamic scope came from a paper by Kathi Fisler, Shriram Krishnamurthi, and Preston Tunnell Wilson.
You may nest a function-calling expression within another.