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 5.5: Functions as Parameters
About This Page
Questions Answered: Could a function operate on other functions? How do I pass a function as a parameter to another function? Why is it great that I can do that?
Topics: The main topic is higher-order functions, especially ones that take in functions as parameters. We’ll touch on a number of other topics as well, including multiple parameter lists, generating collections, and nesting collections within another. Several examples and practice problems involve image processing.
What Will I Do? Read and program. There are numerous small assignments spread throughout.
Rough Estimate of Workload:? Three and a half hours.
Points Available: A50 + B25 + C15.
Related Projects: HigherOrder (new). In one example, we’ll briefly revisit AuctionHouse1, too.
Introduction
We have established that programs feature:
- data — such as numbers, text, and various other objects — which we can store in memory and which we process with:
- operations — functions — that do something with data and may be attached to the data that they operate on (like methods are attached to objects in OOP).
However, this distinction isn’t as clear-cut as it may have seemed so far. It turns out that functions are data, too: we can store a function in a variable, pass a function as a parameter to another function, or have a function return a function.
We’ll soon get to why it might be useful to do that. But first, let’s look at a simple, concrete example.
Passing a Function as a Parameter
Our short-term plan is:
- We’ll define two simple functions —
next
anddoubled
— as examples of operations that take in a single integer and also return an integer. - We’ll define a function named
twice
, which lets us apply any suchInt
-to-Int
function two times. So that this function knows what it should do two times, we pass that function as a parameter totwice
.
Let’s start at Step 1 and write a couple of perfectly mundane functions. Here’s one:
def next(number: Int) = number + 1next: (number: Int)Int next(100)res0: Int = 101
next
is now a function that
takes in an Int
...Int
. In other words, next
is a
function of type Int => Int
, where the Int
to the left of the
arrow is the parameter type and the Int
on the right is the
return type.doubled
is also a function of type Int => Int
:
def doubled(original: Int) = 2 * originaldoubled: (original: Int)Int doubled(100)res1: Int = 200
Defining twice
The twice
function should receive an operation as a parameter and perform that operation
two times. We’d like to be able to use it like this:
twice(next, 1000)res2: Int = 1002
Notice: the first parameter of twice
is a function! That’s how we indicate which
function we want to apply twice. The second parameter is the target of the function
applications; it’s a garden-variety integer.
Applying next
twice produced a number that’s two greater than the input. Doubling
a number twice yields a quadruple:
twice(doubled, 1000)res3: Int = 4000
Now to define twice
:
def twice(operation: Int => Int, target: Int) = operation(operation(target))twice: (operation: Int => Int, target: Int)Int
Int => Int
. That
means that we can (and must) pass in a function that takes in
a single integer and returns an integer. That is, we pass in a
function such as next
or doubled
.twice
first calls the given function on the given integer.
Then it calls the same function another time on the return
value of the first function call.def
to define any function named
operation
. operation
is a regular parameter variable. The
only remarkable thing about it is that it stores a reference
to a function. The command operation(...)
therefore calls
whichever function was passed to twice
this time.twice
refers to a
function, which...Int parameter and returns an `Int
;- takes as its second parameter an integer; and
- returns an integer.
A snack for thought
Compare:
- You can pass a function as a parameter to another function.
- A computer program can take another computer program as input. A compiler, for example, takes in a program and produces a different representation of it. A virtual machine takes in a program and runs it.
- In mathematics, differentiation means taking in a function as “input” and producing a derivative function as “output”.
Higher-Order Functions as High-Level Abstractions
Any function with one or more parameters is an abstraction — a generalization — of
all the concrete cases that result from calling the function on different parameter
values. Our function doubled
, for example, is an abstraction of all possible doublings
of an Int
.
A function that takes another function as a parameter is an abstraction of abstractions.
Our function twice
, for example, is an abstraction of all possible scenarios where we
twice perform an Int
-to-Int
operation such as doubled
or next
.
Functions that receive functions as parameters and/or return functions are known as higher-order functions (korkeamman asteen funktio). In contrast, the ordinary functions you already know can be referred to as first-order functions (ensimmäisen asteen funktio).
Some programming languages support only first-order functions but there are many languages that let us work with higher-order functions, too. Scala is one of the latter, as you’ve already seen.
Additional terms
You may hear programmers speak about “functions as first-class citizens” or “first-class functions”. This refers to precisely the idea that we’ve just introduced: you can store functions in variables and use functions as parameters and return functions from other functions just like you can do the same with, say, numbers. In other words, functions being first-class citizens means that there’s more than just first-order functions available in a language.
Uses for Higher-Order Functions
The twice
function we just wrote isn’t too amazing. Higher-order functions may seem
like a gimmick with little practical significance. That impression is badly mistaken,
however. As we proceed with this and later chapters, you’ll find higher-order functions
to be tremendously useful. The short list of examples below should give a some idea of
what’s coming.
- Scenario:" we want to be able to edit the pixels in an image in
various ways, only some of which we know in advance. For instance,
we want to be able to transform each pixel in a color photo into
grayscale, or soften or brighten an image, or what not. We need a
convenient way to say: “Perform this particular operation on every
pixel in the image.”
- Solution: We call a higher-order method that takes in the pixel-transforming operation as a parameter and performs it on each pixel.
- Scenario: We have an object that represents a button in a GUI.
We want to be able to say: “When that button is pressed, perform
this operation.”
- Solution: we call a higher-order method and pass in a function that will be invoked whenever the button is clicked.
- Scenario: we need a method that sorts a list of objects —
let’s say they are
Person
objects. As part of the sorting algorithm, the method needs to compare two objects (at a time) so as to determine their correct order. We want to have manifold criteria for sorting; we could sort people by their name or by their year of birth, for example. Therefore, we want a convenient way to write: “Sort these objects; here’s how you should compare the objects this time.”- Solution: we call a higher-order sorting method and pass in a function that takes two objects, requests a particular piece of information from each one (e.g., their names), and uses that information to compare the objects.
- Scenario: We have a collection of elements — let’s say each
element is a measurement for a scientific study. We want to
perform diverse operations on this collection, not all of
which we know in advance.
- Solution: we use a collection that has a flexible selection of higher-order methods. For instance, we can tell the collection to apply a particular function to each of its elements.
Later in O1, you’ll see scenarios just like the ones outlined above.
Example: Comparing Strings
Our twice
function takes in a function of type Int => Int
. You can also write
higher-order functions that operate on other kinds of functions, of course. As an
example, consider string comparison.
You can compare strings in different ways. For instance, the three functions below compare two strings by their lengths, by value of the contained numerical characters, and by the strings’ position according to the Unicode “alphabet”, respectively.
def compareLengths(string1: String, string2: String) = string1.length - string2.length
def compareIntContent(string1: String, string2: String) = string1.toInt - string2.toInt
def compareChars(string1: String, string2: String) = string1.compareToIgnoreCase(string2)
Let’s write a function areSorted
that takes in three strings and reports whether or not
they are in the right order. What “right order” means is left for areSorted
’s caller to
decide: as a fourth parameter, the caller passes in a function that compares a pair of
strings according to some criterion.
areSorted
should work like this:
areSorted("Java", "Scala", "Haskell", compareLengths)res4: Boolean = true areSorted("Haskell", "Java", "Scala", compareLengths)res5: Boolean = false areSorted("Java", "Scala", "Haskell", compareChars)res6: Boolean = false areSorted("Haskell", "Java", "Scala", compareChars)res7: Boolean = true areSorted("200", "123", "1000", compareIntContent)res8: Boolean = false areSorted("200", "123", "1000", compareLengths)res9: Boolean = true
And here is an implementation for the function:
def areSorted(first: String, second: String, third: String, compare: (String, String) => Int) =
compare(first, second) <= 0 && compare(second, third) <= 0
compare
parameter is “a function that takes two
strings and returns an integer”.twice
, we
could have but did not need to write (Int) => Int
as the parameter
type.)areSorted
uses the compare
parameter twice to check whether the
values are in order.Example: Searching a Collection
Let’s return for a moment to class AuctionHouse
from Chapter 5.3 and set ourselves
these goals:
AuctionHouse
objects should have a method that we can use to get a list of all the open auctions, that is, all the items that haven’t expired or been sold already.AuctionHouse
objects should have a method that we can use to get a list of all the items whose description contains a given word.- We should be able to similarly request other lists of items that match a criterion. We should be able to select any criterion we choose.
One option would be to write separate methods in AuctionHouse
for each specific need:
findAllOpenItems
, findAllMatchingKeyword
, and so on. But that would mean that we
should correctly anticipate all the ways in which someone might wish to use our class.
A much more flexible solution is to write a generic method findAll
that takes in a
criterion as a parameter and returns a list of all the items that match the given criterion.
We can represent the criterion as a function:
class AuctionHouse {
private val items = Buffer[EnglishAuction]()
// ... other methods here ...
def findAll(checkCriterion: EnglishAuction => Boolean) = {
val found = Buffer[EnglishAuction]()
for (currentItem <- this.items) {
if (checkCriterion(currentItem)) {
found += currentItem
}
}
found.toVector
}
}
findAll
takes a function as a parameter. That function
1) takes an auction as a parameter; 2) works out whether that
auction meets a particular criterion; and 3) returns a Boolean
to indicate whether or not the criterion was met.if
in combination with the
function we got as a parameter.Now we can use our method:
object FindAllTest extends App {
def checkIfOpen(candidate: EnglishAuction) = candidate.isOpen
def checkIfHandbag(candidate: EnglishAuction) = candidate.description.toLowerCase.contains("handbag")
val house = new AuctionHouse("ReBay")
house.addItem(new EnglishAuction("A glorious handbag", 100, 14))
house.addItem(new EnglishAuction("Collectible Easter Bunny China Thimble", 1, 10))
println(house.findAll(checkIfOpen)) // finds both auctions
println(house.findAll(checkIfHandbag)) // finds only the first auction
}
In Chapter 6.2, you’ll see that Scala’s collection classes (such as Vector
) have a
variety of handy higher-order methods that you can use for things like findAll
, and much
more.
Example: Transforming Pixel Colors
The notion of transforming an image by applying an operation to each of its pixels already came up. Here’s an example of such an operation:
def swapGreenAndBlue(original: Color) = Color(original.red, original.blue, original.green)
The Pic
class has a higher-order method named transformColors
. With this method, we can
easily apply this operation to every pixel of an image:
val originalPic = Pic("defense.png")originalPic: Pic = defense.png
val manipulatedPic = originalPic.transformColors(swapGreenAndBlue)manipulatedPic: Pic = defense.png (transformed)
originalPic.leftOf(manipulatedPic).show()
transformColors
takes in a function of type Color => Color
;
here we pass in swapGreenAndBlue
. transformColors
applies
that function to each pixel and returns a new image with
the resulting colors.Assignment: Color Filters
A realistic grayscale filter
An operation that is applied to the pixels of an image is often called a filter (suodin). The above program, for instance, implements a filter that swaps blue with green.
Another example is a filter that turns a color image into a grayscale one. You can
find the code for such a filter in Task1.scala
within project HigherOrder.
Open that file. Read the code, which resembles the other filter that we just wrote. You’ll also find a short task description; do what it asks you to.
A+ presents the exercise submission form here.
Pictures hidden in pictures
What does this picture depict? What about the one a bit further down on the page?
It may not seem like it, but there are meaningful pictures hidden within the pixels of these two images. The images have been deliberately “scrambled” by modifying each pixel’s color components so that the image just looks like a mess to a human viewer. However, the pixels still store a sufficient amount of data from the original unscrambled pictures that we can restore them.
Solve these picture puzzles with Scala code.
First, write a filter that unscrambles the first image by modifying pixel colors. See
Task2.scala
for detailed instructions.
A+ presents the exercise submission form here.
A second picture puzzle
Unscramble the other image, too. The details are in Task3.scala
.
A+ presents the exercise submission form here.
Further reading
If you found this theme interesting, you may wish to read the Wikipedia article on steganography.
Another interesting and slightly scary read is the 2017 study that showed how a criminal might generate audio that sounds innocent to the human ear but actually contains a hidden message that is recognized by a voice-controlled program: Audio Adversarial Examples.
Creating Pictures with a Higher-Order Function
Just like we transformed existing images by applying a function to each pixel, we can apply a function to generate a new image from scratch. There’s a tool for that:
val size = 256size: Int = 256 def blueGradient(x: Int, y: Int) = Color(0, 0, x.toDouble / (size - 1) * Color.Max)blueGradient: (x: Int, y: Int)Color val pic1 = Pic.generate(size, size, blueGradient)pic1: Pic = generated pic pic1.show()
Color.Max
equals the number of different values for
each of the RGB components, which is 256 since the values are
between 0 and 255.)Pic.generate
(a method in Pic
’s companion object;
Chapter 5.1) to produce a new image. We pass in the desired
image’s width and height and a function that will be invoked
on each pixel of the new image to determine its color.show
method displays the image shown on the right.Here’s another example of Pic.generate
. In this example, the formula for selecting
pixel colors is a bit more involved.
def artwork(x: Int, y: Int) = if (x * x > y * 100) Red else if (x + y < 200) Black else if (y % 10 < 5) Blue else Whiteartwork: (x: Int, y: Int)Color Pic.generate(size, size * 2, artwork).show()
Try it. Open Task4.scala
and do the mini-assignment therein.
A+ presents the exercise submission form here.
Go ahead and try generating other images as well.
Interlude: Functions with Multiple Parameter Lists
Before we move on to the rest of the chapter, you should acquaint yourself with a particular feature of the Scala language.
So far, we’ve written all the parameters of a function in a comma-separated list within a single pair of round brackets. In other words, these functions have had a single parameter list (parametriluettelo). Many functions do.
You can also define a Scala function with multiple parameter lists:
def myFunc(first: Int, second: Int)(third: Int, fourth: Int) = first * second + third * fourthmyFunc: (first: Int, second: Int)(third: Int, fourth: Int)Int
myFunc
two parameter lists.
In effect, we’ve grouped the function’s four parameters
in two separate lists.Two pairs of brackets are also needed when we calling that function:
myFunc(1, 2)(3, 4)res10: Int = 14 myFunc(1, 2, 3, 4)<console>:9: error: too many arguments for method myFunc: (first: Int, second: Int)(third: Int, fourth: Int)Int
It’s occasionally convenient to use multiple parameter lists. We won’t really go into that here, though, and in O1 you won’t need to define functions with multiple parameter lists. However, you will at times need to call some of Scala’s library functions that require you to pass in parameters in multiple lists. Our next example features such a function.
Further reading
If you want to find out more about why multiple parameter lists make sense, you can start by reading up on currying on the internet. Warning: the sources you find may not be readily understandable based on what we’ve covered in O1 (because they use either a different programming language or features of Scala that we haven’t discussed).
You may also wish to look into how multiple parameter lists interact with Scala’s type inference.
Creating Collections with a Higher-Order Function
The tabulate
method
Just like we could use Pic.generate
to create pictures, we can use a function to
generate collections of elements. To that end, Scala provides a method named tabulate
.
Recall the two simple functions from the top of the chapter:
def next(number: Int) = number + 1next: (number: Int)Int def doubled(original: Int) = 2 * originaldoubled: (original: Int)Int
Let’s create a vector of integers where each element equals twice its index:
Vector.tabulate(10)(doubled)res11: Vector[Int] = Vector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)
tabulate
takes two parameter lists. The first specifies the
number of elements we want and the second supplies a function
that is called on each index to generate the element for that
index.tabulate
repeatedly calls the function it receives, passing
in each index in turn. Here, doubled
has been called on each
of the numbers from 0 to 9.Here’s a similar example with next
:
Buffer.tabulate(5)(next)res12: Buffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
As you see, tabulate
also works for creating buffers.
More examples of tabulate
tabulate
uses its parameter function on the collection’s indices, which means that the
parameter function must take in Int
s. The function does not, however, have to return
an Int
:
def parity(index: Int) = index % 2 == 0parity: (index: Int)Boolean val parities = Vector.tabulate(5)(parity)parities: Vector[Boolean] = Vector(true, false, true, false, true) println(parities.mkString("\t"))true false true false true
parity
checks whether a given integer is even and returns a
Boolean
.Boolean
s.mkString
method is often useful for formatting
output. Here, by way of example, we’ve used the tabulator
character \t
to separate the elements in the resulting string.And here is a vectorful of more-or-less ascending random numbers:
import scala.util.Randomimport scala.util.Random
def randomElement(upperLimit: Int) = Random.nextInt(upperLimit + 1)randomElement: (upperLimit: Int)Int
println(Vector.tabulate(30)(randomElement).mkString(","))0,0,1,3,4,3,2,1,4,1,0,11,2,13,12,7,6,8,16,4,7,16,14,4,10,24,19,26,15,24
Practice on tabulate
You’ll find a very similar program in Task5.scala
. Read the instructions in the
comments and finish up the program.
A+ presents the exercise submission form here.
“Multidimensional” Collections
Speaking of tabulate
, it sounds like it makes “tables” of things. Why the name?
Presumably, the reasoning behind the name is that tabulate
is a nice way to create
“multidimensional” collections. For instance, say we wish to represent this table of
numbers in our program:
3 | 4 | 5 |
13 | 14 | 15 |
(Readers who have studied mathematics may see this table as a matrix.)
How could we represent this in Scala? Do we need a “two-dimensional vector” with indices for rows and columns separately, or what?
To answer that, let’s first decide how we wish to determine the value in each cell of the table. For this toy example, we’ll use this rather arbitrary function:
def dataAt(row: Int, column: Int) = row * 10 + column + 3dataAt: (row: Int, column: Int)Int
“Multidimensionality” is just nesting
val table = Vector.tabulate(2, 3)(dataAt)table: Vector[Vector[Int]] = Vector(Vector(3, 4, 5), Vector(13, 14, 15))
tabulate
’s first parameter list:
the height and width of the collection.We don’t actually need tabulate
for constructing a two-dimensional collection. We can
also construct one manually:
val twoColumnsFourRows = Vector(Vector(1, 2), Vector(3, 4), Vector(5, 6), Vector(7, 8))twoColumnsFourRows: Vector[Vector[Int]] = Vector(Vector(1, 2), Vector(3, 4), Vector(5, 6), Vector(7, 8))
Frequently asked question: Which index is the row and which is the column?
Answer: That depends entirely on how the programmer has nested the collections in the particular program. You can write a program where each inner vector represents a row and lists the elements in each column of that row; you can just as well write a program where each inner vector represents a column and lists the elements of each row in that column. In this chapter, we have happened to use the former style, but there is no hard-and-fast rule for this.
In fact, it’s not necessary in the first place to use two separate indices and nested collections. We could represent a two-by-three table of numbers with just one single-dimensional vector of six elements, deciding that, say, the indices from 0 to 2 represent the first row and the indices from 3 to 5 the second row. Usually, it’s more convenient to nest collections, though.
Depending on circumstances, how you choose to index a collection can have an impact on efficiency. O1’s follow-on courses will say more about that aspect.
Looping over nested collections
Since a “multidimensional” collection is just a bunch of single-dimensional collections
nested inside an outer collection, there is nothing fundamentally new about using such
a collection. You can process a nested collection just like you’ve processed other
collections. A for
loop works, for instance.
Our next example first uses tabulate
to produce a multiplication table:
def multiply(row: Int, column: Int) = (row + 1) * (column + 1)multiply: (row: Int, column: Int)Int val vectorOfRows = Vector.tabulate(10, 10)(multiply)vectorOfRows: Vector[Vector[Int]] = Vector(Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), Vector(2, 4, 6, ..., 20), , ..., Vector(10, 20, 30, ..., 100))
Suppose we now wish to print out this multiplication table row by row. To do that, we can loop over the outer vector. Each of the inner vectors that it contains represents a row in the table.
for (numbersOnRow <- vectorOfRows) { println(numbersOnRow.mkString("\t")) }1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100
vectorOfRows
is a Vector[Vector[Int]]
, whose elements
are vectors, too, ...numbersOnRow
has the type Vector[Int]
.Merging Pictures with combine
This optional section continues with our image-processing theme and presents additional examples of higher-order methods.
Averaging two images
Suppose we want to combine these two pictures:
val pic1 = Pic("lostgarden/tree-tall.png")pic1: Pic = lostgarden/tree-tall.png val pic2 = Pic("lostgarden/girl-horn.png")pic2: Pic = lostgarden/girl-horn.png
One way to combine images is to compute a pixel-by-pixel average of their color values at each position. That is, at each pair of coordinates, we get the color of both images and apply the following function to get the output color at that position:
def naiveAverage(color1: Color, color2: Color) =
Color((color1.red + color2.red) / 2,
(color1.green + color2.green) / 2,
(color1.blue + color2.blue) / 2)
We also need a way to apply this operation to the target image. That’s easily done
with the combine
method that is available on Pic
objects. This method combines
two images using whichever function we pass to it:
val combinedPic = pic1.combine(pic2, naiveAverage)combinedPic: Pic = combined pic combinedPic.show()
Try the program for yourself; you can find it as Example7.scala
. Try running it
on other inputs as well, if you feel like it.
An image as a stencil for another
Another way to combine two images is to use one of them as a “stencil” or “silhouette” that “selects” a shape in the other image.
The optional assignment in Task6.scala
lets you do that.
A+ presents the exercise submission form here.
More combinations of images
If you enjoyed the previous exercise, you may also want to experiment with this code:
val photo = Pic("kid.png").scaleBy(1.3)
val drawing = Pic("bird.png")
def isBright(color: Color) = color.intensity > 60
def selectColor(c1: Color, c2: Color) = if (isBright(c2)) Black else c1
photo.combine(drawing, selectColor).show()
intensity
method essentially tells you how
bright the color is. Pure White
has an intensity
of 255, for example, and pure Black
an intensity
of zero.What does the resulting image look like and why?
What happens if you give intensity a threshold more or less than 60? Try 20 and 200, for instance.
What happens if you swap c1
and c2
in the body of selectCode
?
Practice on Writing Higher-Order Functions
In each of the three programming assignments that conclude this chapter, you’ll use higher-order functions to work on collections. These assignments differ from earlier ones in that now, you won’t just call higher-order functions but also implement them yourself.
These programs, too, are in project HigherOrder.
Assignment: repeatForEachElement
In this assignment, you’ll implement a higher-order function that takes in a function
and calls that function on each element in a vector of integers. In Task7.scala
, you can
find the beginnings of a function definition and a couple of use cases, but the function
body is missing.
Implement the function so that it works as described. Once you do that, the use cases at
the end of Task7.scala
will also work and produce the specified output.
Instructions and hints:
- The second parameter of
repeatForEachElement
is a function of typeInt => Unit
. Which is why you can pass in functions likeprintCube
andprintIfPositive
when you callrepeatForEachElement
. - You’ll probably want to use a
for
loop.
A+ presents the exercise submission form here.
Assignment: transformEachElement
In this assignment, you’ll write an entire higher-order function. The function should transform the given buffer’s contents by replacing each existing element into a new one that’s determined by the given function.
(This idea is similar to what transformColor
did for images, above, except that you’ll
now modify the existing buffer “in place” instead of generating a new collection.)
For a detailed task description, see Task8.scala
.
A+ presents the exercise submission form here.
Assignment: turnElementsIntoResult
In this assignment, you’ll implement one more higher-order function as well as a couple of use cases for it.
You can think of this assignment as two steps. As Step 1, define turnElementsIntoResult
as instructed in Task9.scala
.
Hint: use a for
loop and a gatherer that tracks the accumulating result. You may also
want to take a look at the animation below.
Once turnElementsIntoResult
correctly produces a sum (which is the use case in the
given code), proceed to Step 2. Follow the instructions to define positiveCount
and
productOfNonZeros
and use those two functions in combination with turnElementsIntoResult
.
A+ presents the exercise submission form here.
On Collections and Higher-Order Functions
The three tasks above featured very generic higher-order functions that enable you to do a great many things with a collection: you can repeat an operation on each element, transform each element to another, or use the elements to compute a result. With higher-order functions such as these, you can operate on collections without having to write loops: you just pass in a function that says what do with each element, and the higher-order function takes care of repetition.
Since functions such as these are so practical, they are also available as part of the Scala API. Very soon, in Chapter 6.2, you’ll see that Scala’s collections have an array of flexible higher-order methods that you’ll find extremely useful. Some of those methods bear a great resemblance to the three functions you just wrote.
But I don’t want to def
all those function names!
Perhaps you find it hard to believe that it could be more practical
to use higher-order functions than loops to work with collections.
Perhaps you find it irritating to define all those parameter
functions separately and to come up with contrived names for each
one (such as printIfPositive
).
It’s true that it’s sometimes a pain to have to name each tiny parameter function. But we’ll salve that pain in the next chapter with anonymous functions.
Summary of Key Points
- Functions are data, too. You can store them in variables, pass them as parameters to other functions, and so forth.
- A higher-order function is a function that operates on one or more other functions. Such functions can implement very generic and useful services: you can call a highly abstract higher-order function and pass in another function that specifies precisely what the higher-order function should do.
- You can nest collections within another collection. This is one way of representing two-dimensional or multidimensional information.
- Links to the glossary: higher-order function; parameter list; filter.
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 grayscale assignment draws inspiration from a similarly themed assignment by Jessen Havill.
The hidden-pics assignments are adaptations of a programming assignment published by Nick Parlante and originally conceived by David J. Malan.
The two pictures in the image-averaging example are by Daniel Cook, who has published them under the Creative Commons Attribution 3.0 license.
The painting from the color-swapping example is The Defense of the Sampo by Akseli Gallén-Kallela.
next
simply adds one to its parameter and returns the result.