A+ will be down for a version upgrade on Thursday October 17th 2024 at 09:00-12:00.
This course has already ended.

The latest instance of the course can be found at: O1: 2024

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. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.

Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 1.7: Creating Custom Functions

About This Page

Questions Answered: How do I write a Scala function? How does a function do what it does? How can I make a function use the values that it receives as parameters?

Topics: Defining functions in program code: def, parameter variables, the function body, returning a value, local variables. The stages of executing a function; the call stack and frames.

What Will I Do? Read and practice. Program both in the REPL and outside of it.

Rough Estimate of Workload:? A couple of hours. For many students, this will be the most challenging chapter thus far. Even though we don’t do anything particularly complex yet, your first steps as a programmer may be coltish, and that’s normal. It will also turn out that Scala’s punctuation can be a bit involved.

Points Available: A90.

Related Projects: Subprograms.

../_images/sound_icon.png

Notes: This chapter makes occasional use of sound, so speakers or headphones are recommended. They aren’t strictly necessary, though.

../_images/person01.png

Introduction

To experiment with the functions that we’ll discuss in this chapter, start up your REPL as you did in the previous chapter, select Subprograms, and enter import o1._ and import o1.subprograms._ (yes, both).

Some of the functions in that package we already used in the previous chapter. In this chapter, we’ll return to some of those functions and introduce some new ones. But what’s really new here is that now we’ll be taking a look at the program code that implements the functions.

The program code for the chapter is available in o1/subprograms.scala within the project, in case you want to take a look.

Defining a Function

The average function is familiar to us from Chapter 1.6. We can use it in the REPL as shown here:

average(2.0, 5.5)res0: Double = 3.75

In order for that command to work, the averaging function needs a definition. This example function’s definition is fairly simple but nevertheless introduces a number of important concepts.

In English, the function’s operating principle is: “When the function average is called, it produces a return value by adding the value of the first parameter to the value of the second parameter and dividing the sum by two.” This principle is captured in Scala below.

(Side note: From here on in, our examples will gradually grow in complexity, and many of them will be displayed in boxes such as this one, with different parts of the program highlighted in different colors. As you will have noticed, Eclipse does something very similar albeit in different colors.)

def average(first: Double, second: Double) = (first + second) / 2
A function definition begins with the def keyword followed by a name for the function, as chosen by the programmer. In Scala, it’s customary to start function names with a lower-case letter, just as we did earlier with variables.
We state: “when calling the function average, one must pass two Double values as parameters”. Speaking more generally, this is where we define the function’s parameter variables (parametrimuuttuja; also known as formal parameters) and their data types. In Scala, the names of data types are usually capitalized.
Each parameter variable has a name. In our example, we’ve chosen simply to call the first parameter variable first and the second second.
Make sure you notice the equals sign, which is followed by...
... the actual implementation of the function, known as the function body (funktion runko). The function body implements the algorithm that accomplishes whichever task the function is intended to accomplish. In our simple example, the body is just a single arithmetic expression that instructs the computer what it needs to do to obtain the return value.

Note that the parameter variables first and second genuinely are variables — val variables, to be more specific. Parameter variables don’t receive values from an assignment command but indirectly through function calls; apart from that, though, you can use them just like the other variables that you’ve seen.

Function Execution and the Call Stack

The following animation shows another diagram of data in computer memory. The diagram evolves step by step as the program runs. This time, we’ll also inspect what happens within a function: you’ll see how parameter variables work, for instance. Examine this animation thoroughly! It introduces concepts that play crucial roles in the programs that we’re about to write.

The piece of code illustrated in the animation doesn’t accomplish anything much. It just calls the averaging function a couple of times.

The animation featured frames (kehys). The frames formed a call stack (kutsupino), which is often known simply as the stack and which indeed works like a real-world stack of items: when a function call begins, a new frame is added to the top of the stack, and when a call terminates by returning a value, that topmost frame gets removed from the stack. The computer uses the frames of the call stack to keep track of which function calls are active and what data is associated with each of those calls. At any given time, it’s the topmost frame of the stack that is active, while the lower frames (of which there was just one in this animation) wait for the frame above to finish its task before resuming theirs.

Hopefully, the animation already helped you make sense of how a function call unfolds. Be that as it may, the significance of the call stack and its frames will soon be highlighted as we take on some more complex functions.

A function call... is what exactly?

Chapter 1.2 mentioned that we use the word “program” in reference to static program code (as in “The program has 100 lines.”) but also in reference to the dynamic processes that take place when programs are run (as in “The program stored the information entered by the user.”).

“Function call” is similarly ambiguous. Sometimes we mean an expression written in code: “The function call on line 15 has a punctuation error.” And sometimes we refer to processes like the one in the preceding animation, which take place when a function is executed: “The function call is over and yielded a return value of 4.”

Be wise to both meanings as you go through this ebook and other resources on programming.

Practice on Defining a Function

Let’s define a simple effect-free function that you can use like this:

yell("Yippee")res1: String = Yippee!
val result = yell("Muhaha")result: String = Muhaha!
val louder = yell(result)louder: String = Muhaha!!

Here’s a template for the function definition. Five parts have been “left blank” and marked with three question marks each.

??? ???(phrase: ???) = ??? + ???

Fill in the blanks: replace the question marks so that the function can be used as in the REPL example. Each of the five blanks calls for a different code fragment. You can model your answer on the average function shown above.

Enter the function’s full definition here:

Programming Exercise: meters

Task description

In the United Kingdom and some of its former colonies, feet and inches are still widely used for measuring length. One inch equals 2.54 cm, and one foot is 12 inches. Measurements are commonly expressed as a combination of these units. For instance, a person who is about 180 cm tall might be described as “five foot eleven”: five feet plus eleven inches.

Implement an effect-free Scala function that:

  • is called toMeters;
  • takes in, as its first parameter, a number of feet represented as a value of type Double;
  • similarly takes in a number of inches as its second parameter; and
  • computes and returns a Double equal to the total length in meters of the given feet and inches (e.g., given the values 5 and 11, returns 1.8034).

Proceed in the following stages: set up the assignment, write some code, test. Keep adjusting and testing your program until it works.

There are some additional instructions for each stage below.

Setup

In Eclipse, find the Subprograms project and its file o1/subprograms.scala. Near the top of the file, there is a comment that shows you where you’ll be writing your code as you work on this chapter.

Writing your function

  1. First, make sure you understand precisely what the function is supposed to do! This sounds self-evident, sure, but it’s easy to forget. You can lay substantial pain upon yourself just by overlooking something simple in the task description.

  2. Write the function definition in the indicated section of the file. In this case, one line is enough.

    1. Start with def and the name of the function.
    2. Type in the parameter definitions. Choose sensible names for the parameter variables.
    3. Write the function body. A single arithmetic expression will do to convert between the units. (Don’t print out the resulting value; simply return it!)

    On error messages in Eclipse

    As you edit your code, you’ll probably see some saw-toothed highlights error_underline and other annotations in red. This is Eclipse’s way of letting you know that it has spotted problems. But don’t be flustered even if your code lights up in red as soon as you start writing. Eclipse adjusts these annotations constantly as you type, and incomplete code often prompts a complaint. For instance, as soon as you’ve typed in the word def, Eclipse will cry out, noticing that your function definition is incomplete. The red highlights can be a great help, and you should take them seriously, but only if they remain after you consider your toMeters function to be ready.

    Below Eclipse’s editor, you’ll find a Problems tab that displays some additional information about each highlighted error. You can also view the same messages by hovering your mouse cursor over the error_symbol symbols at the edge of the editor. Unfortunately, it can be hard to understand what each error message means if you’re going by just what we’ve covered in O1 so far; at this stage, you may find it easier to fix your code by comparing it to the ebook’s examples.

  3. Take care that no red error annotations remain. In particular, eliminate any errors in punctuation. Did you use at least round brackets, a comma, and an equals sign? Were you consistent in how you wrote the name of each variable?

  4. Remember to save the file!

Note:

Before running a program you wrote, always make sure you have gotten rid of the errors highlighted by Eclipse. The next step of testing your code will surely fail if you don’t!

Testing your function

  1. Take out your REPL (e.g., Alt+F11). Choosing Subprograms.

  2. Import your function with import o1.subprograms._ or import o1.subprograms.toMeters.

  3. Try calling your function on different parameter values. Does it work right? If not, return to Step 1 of Writing your code above (yes, to Step 1, not straight to Step 2). Ask the O1 staff for help as needed.

    (P.S. Did you notice that the result is expected in meters, not in centimeters?)

    On “rebooting” the REPL

    Whenever you edit your program and wish to test the new version in the REPL, you need to relaunch the REPL. You can, for instance:

    • close the REPL and start an entirely new session as per usual; or
    • press the stop button at the top right-hand corner of the REPL. It seems to do almost nothing, but behind the scenes it does reset the ongoing REPL session so that the effects of any commands you have previously entered, imports included, no longer apply.

    Next, re-enter the import command to access the new version of your program. Remember that while holding down the Ctrl key (Ctrl+Cmd on a Mac), you can use the arrow keys to access any commands you previously entered during the current REPL session.

    Experiment with the other buttons at the top right of the REPL. The Relaunch Interpreter and Replay History button, for instance, relaunches the REPL and automatically reruns all the commands you gave before the “reboot”. This can be very convenient after you’ve made a change to a function and want to retry the earlier commands on the new version.

Submission form

When you’re confident that your function works, enjoy the moment, then use the form below to submit your solution.

A+ presents the exercise submission form here.

More simple functions

If you wish, you can build some programming routine with this voluntary and easy practice task that greatly resembles the previous assignment.

In the same file, subprograms.scala, write the following functions:

  • volumeOfCube, which expects a single parameter that represents a cube’s edge length and returns the cube’s volume. The parameter and the return value are both Doubles.
  • areaOfCube, which similarly computes and returns a cube’s area, given its edge length.

A+ presents the exercise submission form here.

Defining Functions within the REPL

In the previous assignment, you used Eclipse’s editor to write and save your functions in a separate file. That’s what we’ll be doing in upcoming assignments as well. However, it’s good to realize that you can also enter function definitions straight into the REPL:

def average(first: Double, second: Double) = (first + second) / 2average: (first: Double, second: Double)Double
average(2, 1)res2: Double = 1.5

This is convenient in that you don’t have to “reboot” the REPL as above or enter any import commands. Howevever, using Eclipse’s editor is better preparation for future assignments and makes for convenient submissions. Saving files in the editor also lets you keep the code you wrote.

As you experiment with Scala commands on your own, feel free to define functions in the REPL.

Practice on Functions and Modulo

The modulo operator

In addition to the everyday arithmetic operators, programmers commonly employ the modulo operator, which is written in Scala as %. This operator is usually associated with integer numbers; one might evaluate the expression 25 % 7, for example.

Now go and experiment with the modulo operator on your own in the REPL.

Which of the following options best describes what the modulo operator does?

Example: modulo and parity

For various reasons, we may find it useful to determine a number’s parity: whether it is even or odd. We might, for instance, wish to do something to every other element of a list, such as coloring the even and odd rows of a large table in a different background colors. Right now, we’re going to use parity simply as an additional example of defining a custom function and applying the modulo operator.

Let’s create an effect-free function that returns zero to signal that a given number is even. Otherwise, it will return either 1 or -1, matching the sign of the given number. Like so:

parity(100)res3: Int = 0
parity(2)res4: Int = 0
parity(103)res5: Int = 1
parity(7)res6: Int = 1
parity(-7)res7: Int = -1

The function is easily implemented with the modulo operator: if we divide a number by two, we get a remainder of zero if and only if the number was even. Here’s a Scala implementation:

def parity(number: Int) = number % 2

Go ahead and define the function and experiment with it in the REPL, if you want. Or continue right away to the next assignment.

Chess boards: introduction

../_images/shakki1.png

Let’s say we’ve numbered the squares on a chess board from 1 to 64 starting at the top left and progressing from left to right one row at a time. Moreover, we have numbered the rows and columns (ranks and files in chess lingo) from 1 to 8 each. There’s a picture right there.

If we know the number of a square, such as 35, how do we get the computer to work out which row and which column the square is at? One possibility is to define two effect-free functions that can be used as follows.

row(35)res8: Int = 5
column(35)res9: Int = 3

Here are the function definitions:

def row(square: Int) = ((square - 1) / 8) + 1

def column(square: Int) = ((square - 1) % 8) + 1

Chess boards: an assignment

../_images/shakki2.png

But what if we had instead numbered the squares from 0 to 63 and the rows and columns from 0 to 7, as shown in this picture? Write two effect-free functions, row and column, that work like this:

row(34)res10: Int = 4
column(34)res11: Int = 2

Instructions and hints:

  • Use the same workflow as in the toMeters assignment.
  • These functions, too, should go in subprograms.scala.
  • If you understand the given versions above, where numbering runs from one upwards, this isn’t a difficult assignment. Under the zero-based numbering scheme, the solution actually simpler.
  • Test your code in the REPL before you submit. Remember to import.

A+ presents the exercise submission form here.

Consecutive Commands in a Function

Just to try out something, let’s create a function that prints out the same four lines of text every time it’s called, followed by a fifth line that the function’s caller must pass in as a parameter. We intend to be able to use our function, careerAdvicePlease, like this:

careerAdvicePlease("I don't know what to do.")Oppenheimer built the bomb, but now he's dead. (Dead!)
Einstein was very, very smart, but not enough not to be dead. (Dead!)
So don't go into science; you'll end up dead.
Don't go into science; you'll end up dead.
I don't know what to do.
careerAdvicePlease("Such is the human condition.")Oppenheimer built the bomb, but now he's dead. (Dead!)
Einstein was very, very smart, but not enough not to be dead. (Dead!)
So don't go into science; you'll end up dead.
Don't go into science; you'll end up dead.
Such is the human condition.

Here is the function definition:

def careerAdvicePlease(addendum: String) = {
  println("Oppenheimer built the bomb, but now he's dead. (Dead!)")
  println("Einstein was very, very smart, but not enough not to be dead. (Dead!)")
  println("So don't go into science; you'll end up dead.")
  println("Don't go into science; you'll end up dead.")
  println(addendum)
}
The careerAdvicePlease function is defined over multiple lines of code. In cases like this, we place the lines within curly brackets. What this means is that each of the five println commands gets executed, in order, whenever careerAdvicePlease is called.
This is also our first proper example of a code construct that is split over multiple lines. In Scala, like in many other textual programming languages, it’s customary to indent (sisentää) the code as shown.

In a technical sense, Scala is quite permissive about whitespace, but it’s good programming style to use space to clarify the relationships between pieces of program code. Indenting is part of that. In Scala, indentations that are two spaces wide are common.

What about the return value?

Our careerAdvicePlease function returns the contentless Unit value, or “returns nothing”, so to speak. This is because the command in the function body that is executed last determines the return value of the entire function. In our example, the last command to be executed is the last of the printlns, which yields only a Unit value (as discussed in Chapter 1.6). Therefore, the same Unit value is also returned by the careerAdvicePlease function that calls println.

Punctuation in Function Definitions

There are a number of punctuation rules that govern function definitions in Scala. Newcomers to the language may find these rules distasteful at first, and indeed such rules aren’t the most delicious dish that programming has to offer. Nevertheless, we do need to take a moment to ingest them. For starters, let’s have equals:

The equals sign

Whenever you define a function, always remember to write the equals sign between the parameter list and the function body. Forget to do so, and you may be faced with some rather cryptic error messages or elusive bugs.

Curly brackets and line breaks

In the careerAdvicePlease function, it was necessary to use the curly brackets because we wanted to group multiple print commands in the same function. There is in fact nothing preventing us from using curly brackets and line breaks even in smaller functions. For instance, all three implementations of average below do the same thing.

Version 1:

def average(first: Double, second: Double) = (first + second) / 2

Version 2:

def average(first: Double, second: Double) =
  (first + second) / 2

Version 3:

def average(first: Double, second: Double) = {
  (first + second) / 2
}

Many programmers have adopted the following conventions for writing functions in Scala:

  • If the function body consists of a single expression and the function is effect-free (Chapter 1.6), the curly brackets are omitted.
    • If the expression is short it can be placed on the same line with the def keyword. Version 1 is therefore the recommended way to write average.
    • If the line would become excessively long, a line break is inserted after the equals sign (as in Version 2).
  • If the function body contains multiple commands or if the function is effectful (e.g., it prints out something), line breaks, curly brackets, and indentation are used, as we did in careerAdvicePlease and in Version 3 above.

We generally follow these style conventions in this ebook. We recommend that you do the same. You may find the conventions confusing at first, but don’t let that bother you too much when writing your own programs. If it feels easier for you, go ahead and format all your functions as in Version 3. It’s always okay to write the curly brackets!

Summary of punctuation rules

Below is the summary table from the previous chapter with added notes on punctuation. Hover the mouse over the underlined bits to view additional explanations.

Does the function affect state? Does it return a value? The term we use in O1 Use an equals sign in front of the function body? Use curly brackets around the body? Use line breaks and indentations?
Never Yes Effect-free function Yes! At least if the body has multiple commands. Always where you have curly brackets. Also used for splitting long one-liners.
No (If a function doesn’t have an effect on program state or return a value, it can’t be very useful, barring some special circumstances. You won’t need to write such an empty function in O1.)
At least sometimes Yes Effectful function Yes! Yes Yes
No Effectful function Yes Yes Yes

How do I know if my function is effectful?

Imagine the scene. Your program is running and is in a particular state: certain things have been stored in memory and something is displayed onscreen. A function is then called. It does its job and produces a return value; a bit of time passes. Apart from that, is the program back in the state where it was before the function call?

  • If the called function is, say, average, we are effectively back to where we were: we obtained a result but nothing else changed. This is an effect-free function.
  • If the called function is, say, println, we aren’t back to the original state: the program has generated a line of output. This is an effectful function.

Here’s another way to think about it: if the program had access to the return value of the function already, would it even be necessary to call the function? In the case of our effect-free averaging function, the answer is no: we could have just as well replaced the call average(5, 10) with the literal 7.5. In the case of the effectful println, on the other hand, we don’t get any printing done without calling the function.

Assignment: Text Accompanied by Music

Task description

Write an effectful function called accompany that prints out some text and accompanies it with sound. The function should take two parameters, each of which is a string: the function prints out the first string and plays the second string.

When you’re done, it should be possible to use the function as in this example.

accompany("Nananana nananana nananana nananana BATMAAN!",
          "[49]<" + "(d<g>)(d<g>)(db<g>)(db<g>)(c<g>)(c<g>)(c#<g>)(c#<g>)" * 2 + "[54]>(FG)-.(FG)---/160")Nananana nananana nananana nananana BATMAAN!
accompany("... and introducing acoustic guitar",
          "[26]    >EF#A---G#---F#---E---F#G#---------EF#A---G#---F#---E---F#EF#---------/192")... and introducing acoustic guitar

The line breaks in the example are there just to prevent overlong lines. They aren’t strictly necessary.

Instructions and hints

  • Write your solution in the same file, subprograms.scala.
  • Pick sensible names for the parameter variables.
  • In this assignment, the order in which you call println and play within the function body has no practical significance. Take your pick, as long as you do both.
    • The play function has been defined so that it plays the notes in the background and the computer doesn’t wait for the sound to finish before executing the next command. So the printout will appear promptly even if you place the play command before the println.
  • Follow the guidelines on programming style given above and split your code over multiple lines. Remember to punctuate.

Submission form

A+ presents the exercise submission form here.

Tools for Building New Functions

The preceding examples have shown that when you implement a function body, you can use the programming techniques that you know from earlier chapters: arithmetic operators, println, play, etc.. Other familiar commands work, too. For instance, a bit further down in this chapter, we’re going to define new variables within a function. Throughout O1, you’ll learn additional tools for building your own functions: you’ll be able to make functions choose between alternatives, execute commands repeatedly, and so on.

One very common thing to do in a function is to call another function. For instance, we can use the hypotenuse function from package scala.math to create a function that determines the distance between two points (x1,y1) and (x2,y2):

def distance(x1: Double, y1: Double, x2: Double, y2: Double) = hypot(x2 - x1, y2 - y1)

The picture functions from package o1, introduced in Chapter 1.3, are also available to us as we create new functions:

A picture-creating function

This function produces red “balls” of different sizes:

def redBall(size: Int) = circle(size, Red)

A usage example:

import o1._import o1._
val ball = redBall(50)ball: Pic = circle-shape
val biggerBall = redBall(300)biggerBall: Pic = circle-shape
show(biggerBall)

Vertical bars

In subprograms.scala, create a function called verticalBar that forms pictures of blue rectangles that are ten times as many pixels high as they are wide.

The function should take a single parameter of type Int, namely the width of the bar. Here’s a usage scenario:

val picOfBar = verticalBar(80)picOfBar: Pic = rectangle-shape
show(picOfBar)show(verticalBar(180))

Give a meaningful name to the parameter variable. Use show to test your function in the REPL. Note that your function must not call show and display the picture; it must return the picture. Then you’ll be able to use your function and show in combination as illustrated above.

A+ presents the exercise submission form here.

An overloaded function

Continue from the previous assignment by creating another verticalBar function. This is, do not erase the original you just wrote but add another function with the same name. This new function should take two parameters, a width (Int) and a color (Color). It should be usable like this:

val blackBar = verticalBar(80, Black)blackBar: Pic = rectangle-shape
show(blackBar)

Clearly, this second verticalBar function will be more flexible (more abstract) as far as color is concerned. On the other hand, it requires its caller to pass in an additional parameter. This second function, too, returns a picture that’s ten times as high as it’s wide.

A+ presents the exercise submission form here.

As you see, Scala allows us to define multiple functions with the same name, even in the same context. This is called overloading (kuormittaa) the function name. Overloading is allowed only if the functions that share a name differ in which parameters they take.

Despite their shared name, overloaded functions (here: the two verticalBar functions you wrote) are completely distinct from each other. When you use the functions’ name, the parameter expressions that you provide determine which function gets called.

Practice on Understanding Functions

For each function, select the options that correctly describe it.

Here’s the first function:

def distance(x1: Double, y1: Double, x2: Double, y2: Double) = hypot(x2 - x1, y2 - y1)
def redBall(size: Int) = circle(size, Red)
def experiment1(number: Int) = {
  println("The number is: " + number)
}

In the next function, notice the type of the parameter variable: the caller must provide a reference to a buffer that contains integers.

def experiment2(numbers: Buffer[Int]) = {
  numbers(0) = 100
}
def experiment3(number: Int) = {
  println("The number is: " + number)
  number + 1
}

Which of the following best describes what happens when a function call is made (as per the model of program execution introduced in this chapter)? Review the animations at the beginning of the chapter, if necessary.

As an additional exercise, see if you can envision a scenario where it makes a difference when the parameter expressions are evaluated: whether evaluation happens before the function call starts or only during the function call.

Local Variables

Example: tax rates

Let’s write an effect-free function that calculates how much income tax one has to pay under this simple system of taxation:

  • Until a specific threshold income, one pays a base percentage of one’s income as tax (e.g., 20 percent), as set by the government.
  • In addition, one must pay a higher percentage (e.g., 40 percent) of all that one earns above the threshold (if anything).

When invoking our function, incomeTax, we’d like to pass in the earner’s total income, the threshold income, the base rate of taxation, and the additional rate, as shown below.

incomeTax(50000, 30000, 0.2, 0.4)res12: Double = 14000.0
incomeTax(25000, 30000, 0.2, 0.4)res13: Double = 5000.0

It would be possible to define this function as a one-liner, whose body consists of a single expression that evaluates to the function’s return value. But we can do better than that. Let’s store intermediate results in variables so that our code is a bit neater.

def incomeTax(income: Double, thresholdIncome: Double, baseRate: Double, additionalRate: Double) = {
  val baseTax = min(thresholdIncome, income)
  val additionalTax = max(income - thresholdIncome, 0)
  baseTax * baseRate + additionalTax * additionalRate
}
We define two local variables (paikallinen muuttuja) and assign intermediate results to them.
Recall that when a Scala function consists of multiple commands, the last command determines the return value of the function. Our example function returns the value of the arithmetic expression on the last line. The two lines above it set up that final expression.

Local variables are accessible only within the program code of the function itself. A function’s caller doesn’t need to be aware of the called function’s local variables, nor could the caller use them even if they were so aware. Any user of incomeTax, for instance, can remain blissfully unaware of what the function’s local variables are called and indeed the fact that the function has any local variables to begin with. And the following attempt to tamper with the function’s internal variables is unsuccessful, irrespective of whether we have previously called incomeTax.

additionalTax * additionalRate<console>:14: error: not found: value additionalTax
            additionalTax * additionalRate
            ^

As a matter of fact, the local variables don’t even exist during the entirety of the program run but only while the function is being executed. Local variables are created within the frame associated with the function call on the call stack. As soon as the function call is over, any memory reserved for the variables is released for other use. This is depicted in the animation below.

More function-reading practice

Consider how local variables were represented in the preceding animation. Then take your best shot at assessing this claim: “Parameter variables are local variables, too.”

For each function below, select the options that correctly characterize the function.

def incomeTax(income: Double, thresholdIncome: Double, baseRate: Double, additionalRate: Double) = {
  val baseTax = min(thresholdIncome, income)
  val additionalTax = max(income - thresholdIncome, 0)
  baseTax * baseRate + additionalTax * additionalRate
}
def experiment4(word: String) = {
  var number = 1
  println(word + ": " + number)
  number = number + 1
  println(word + ": " + number)
  number = number + 1
  println(word + ": " + number)
  number
}
def experiment5(initial: Int) = {
  var number = initial
  number = number + 1
  number = number + 1
  number = number + 1
  number
}

In Chapter 1.6, you used a function called canon. Let’s undertake to create a function that is somewhat simpler in its implementation but similar in purpose. Our new function should play a given melody on two instruments, using one instrument after the other has finished (not overlapping as with canon). Here’s how we intend to use it:

val duet = onTwoInstruments("g#e--f#c#----", 26, 66, 5)duet: String = [26]g#e--f#c#----     [66]g#e--f#c#----
play(duet)
The function returns a string that contains two copies of the melody specified by the first parameter.
The second and third parameters indicate the dueting instruments. Our function uses these parameters to insert the appropriate markup into the string that it returns, such that play will interpret them as an instruction to play the melody first on one instrument, then on the other.
The last parameter defines the length of the pause between the two instruments.

Consider this implementation of onTwoInstruments, which you can also find in subprograms.scala. It almost works.

def onTwoInstruments(melody: String, first: Int, second: Int, lengthOfPause: Int) = {
  val melodyUsingFirst  = "[" + first  + "]" + melody
  val melodyUsingSecond = "[" + second + "]" + melody
  val pause = " " * lengthOfPause
  val playedTwice = melodyUsingFirst + pause + melodyUsingSecond
}

That implementation contains a typical beginner’s mistake. Inspect the code carefully; you can also try calling it in the REPL. Among the options below, choose all that apply. If you want, you can also fix the function’s code within the project.

Exercise: Course Grades

Task description

Create an effect-free function that determines a student’s overall course grade according to a specific course policy. In our imaginary example course, the overall grade is the sum of a project grade (between 0 and 4), a bonus from an exam (0 or 1), and a bonus for active participation (0 or 1). However, the overall grade can never exceed 5.

Your function must adhere to the following specification.

  • Its name is overallGrade.
  • As parameters, it expects three integers: a project grade and the bonuses for exam and participation, in that order.
  • It returns the corresponding overall grade between 0 and 5, also as an integer. (It doesn’t print the grade but returns it.)

You must ensure that even if the sum of the components is more than five, the overall grade isn’t. However, you don’t need to concern yourself with what happens if someone were to call your function on invalid parameter values (such as a negative project grade or an excessive exam bonus).

Instructions and hints

  • Once again, write your code in subprograms.scala.
  • Follow the now-familiar workflow recapped below.
    1. Make sure you understand precisely what the function is supposed to do!
    2. Implement the function in stages: name, parameter variable (with sensible names!) and their types, function body.
    3. Reset your REPL and import your function.
    4. Test your function on a variety of parameter values. Go back to Step 1 if needed.
    5. Submit your program only when you’re satisfied that it works.
  • Chapter 1.6 introduced a mathematical function that was already used in this chapter too and that you’re likely to find useful.

Submission form

A+ presents the exercise submission form here.

Summary of Key Points

  • A function definition includes the function’s name, its parameter variables and their data types, and a function body; all in all, it is an implementation for an algorithm that takes care of whatever the function is expected to do.
  • When a function call starts, the computer reserves a so-called frame of memory to track the call.
    • The parameter values received by the function are stored in variables within the frame.
    • You can also define additional variables in the frame. Such a variable is known as a local variable.
    • Frames form a call stack (which we’ll discussed further in the next chapter).
  • Links to the glossary: function, function call, function body; frame, call stack; local variable, parameter variable, to overload.

The diagram below includes a handful of concepts that are central to how functions work on the inside.

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 that has contributed to this ebook’s design. Thank you!

Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.

Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.

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 programmed by Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä, 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 have done 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 and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy of using tools from O1Library (such as Pic) for simple graphical programming 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+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits for this page

The “career advice” comes from The Arrogant Worms.

../_images/imho1.png
Posting submission...