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 6.2: Anonymous Functions
Introduction: A Few Words about Literals
In this chapter, you won’t learn to create new sorts of programs. What you will learn is new ways of writing functions. These new notations aren’t a theoretical exercise but will help you program more efficiently.
But let’s not go there just yet. Let’s start with numbers.
Already in Week 1, you learned to store numbers in variables. You can use a variable name to refer to a number, which is convenient especially when you need to refer to the same number in multiple places within your program.
On the other hand, we don’t always use variable names to refer to numbers. If we have
a var
named index
and wish to increment its value by one within a loop, we typically
won’t write this:
val incrementSize = 1
index += incrementSize
Instead, we’ll probably use the integer literal 1
simply to write index += 1
. As
you know, a literal is an expression that expresses a specific value “directly” or
“literally” as written in code. There are many reasons to use a literal in index += 1
:
A human reader can easily make sense of “incrementing by one” as a single whole.
The purpose of
1
inindex += 1
is readily apparent from the command, so introducing an additional variable and its nameincrementSize
doesn’t really make the program any more readable.incrementSize
is (presumably) a single-shot variable that isn’t used anywhere else in the program.If we don’t have
incrementSize
, readers of our program code don’t have to consider whether the variable might have some broader meaning in the program apart from its use inindex += incrementSize
.Given all of the above,
index += 1
is a shorter way to express the same thing without being harder to read. (In fact, it may be easier.)
Enough about numbers. How is this relevant to functions?
Since Week 1, you have used def
to define functions that have names. You have used the
functions’ names to refer to particular operations much like you have used variables’
names to refer to particular numbers. Function names are especially convenient when you
want to call the same function multiple times, perhaps from different parts of the program.
In this chapter, you’ll see that you can also define anonymous, “single-shot” functions.
For that, we need function literals.
Function Literals
In Chapter 6.1 we discussed the following example function, which calls its parameter function twice.
def twice(operation: Int => Int, target: Int) = operation(operation(target))
We used this function to twice add one to a number, and to twice double a number. So we defined and named these two mini-functions:
def next(number: Int) = number + 1
def doubled(original: Int) = 2 * original
Here’s how we used twice
in Chapter 6.1:
twice(next, 1000)res0: Int = 1002 twice(doubled, 1000)res1: Int = 4000
There is an easier way to accomplish the same thing. Let’s suppose we’ve defined twice
as above. However, we’ll now discard next
and doubled
; we won’t need them if we define
functions “on the fly” as we pass them to twice
.
twice(number => number + 1, 1000)res2: Int = 1002
A rightward arrow indicates that this is a function literal. The parameters of the anonymous function are listed on the left (here, we have only one) and body of the function is written on the right (here, it’s a simple sum).
We can similarly use a function literal to double a number twice:
twice(n => 2 * n, 1000)res3: Int = 4000
The function literal defines an anonymous function that returns a value twice as big as its input.
We could have written original
or some other name in place of
n
, here. Where it doesn’t cause confusion, it’s common to use
simple names for the parameters of function literals in order to
keep the literal short and crisp.
Writing a function literal
The arrow: function literals contain the same sort of arrow that you already know
from function types (e.g., the Int => Int
in the definition of twice
).
Parameter types in a literal: you can often omit the parameter types of a function
literal because Scala can work out the types from the context. For instance, in our
example above, we didn’t have to explicitly tell the computer the type of number
in
number => number + 1
. Given that we passed that function literal to twice
, which
expects a function of type Int => Int
, it can be inferred that number
is an Int
.
As said, the computer can very often infer parameter types automatically. When that isn’t
the case, you can explicitly mark down the types just like you’ve done with named functions:
use round brackets and colons. For example, you can write the literal number => number + 1
in full as (number: Int) => number + 1
.
Brackets: when a function literal takes only a single parameter whose type can be inferred, you can omit the round brackets that usually surround parameter lists. Which is what we did in our examples above. If you have multiple parameters, you must write the brackets. (See below for examples.) It’s never wrong to write the brackets.
Anonymous functions as values
Let’s use the REPL to examine how function literals turn into anonymous function objects.
Here’s an increment-by-one literal:
(number: Int) => number + 1res4: Int => Int = <function1>
The REPL confirms: we have a value of type Int => Int
.
Here, we actually needed an explicit type annotation for
number
, because there is no context to indicate its type.
(For all Scala knows, we might have intended for number
to be a Double
or a String
, for instance.)
The literal below defines a function that takes two parameters, divides one by the other,
and returns a rounded result. It has the type (Double, Double) => Int
.
(x: Double, y: Double) => (x / y).round.toIntres5: (Double, Double) => Int = <function2>
References to anonymous functions
A function defined by a function literal is an object. You can use it like you use other objects.
The function itself has no name. However, we can make a variable refer to an anonymous function:
val myFunc = (x: Double, y: Double) => (x / y).round.toIntmyFunc: (Double, Double) => Int = <function2>
Now we can use the variable’s name to call the function:
myFunc(10, 4)res6: Int = 3
It’s also perfectly possible to have another variable refer to the same function:
val roundedDivision = myFuncroundedDivision: (Double, Double) => Int = <function2>
Now this works too:
roundedDivision(100, 8)res7: Int = 13
Functions as objects
In Chapter 5.3, you saw that if a Scala object has a method named
apply
, it serves as a sort of “default method” for that object. For
instance, the expression myObject(params)
is simply a shorthand
notation for the method call myObject.apply(params)
. In effect, you
can use an object with an apply
method “as if it was a function”.
All of Scala’s function object have an apply
method, and the
following command means precisely the same as the one above.
roundedDivision.apply(100, 8)res8: Int = 13
From an object-oriented point of view, to call a function is to call
its apply
method.
Why use function literals and anonymous functions?
The strengths and weaknesses of function literals are similar to those of, say, integer literals. Here are some of the strengths:
You don’t need to define names for every last tiny function that you use only once.
You can write the function literal precisely where you need the function. For example, you can write a literal within a
twice
call, so that your function definition is affixed to what you do with the function. This can make your code more readable, especially if the function literal is short.Function literals can generally make your code shorter. Brevity is not an end in itself, but it’s a nice bonus unless it results in cryptic code.
Limitations and weaknesses:
Anonymous function have no names. Sometimes, the lack of names makes code harder to read.
Writing and reading function literals can be a challenge at first (but it soon gets easier with practice).
There are many functions that need a name, such as the public methods on objects. Anonymous functions don’t work for all needs.
If we wish to call an anonymous function multiple times, we need a variable (a name) that we can use to refer to the function.
Anonymous functions need to be used with care. One excellent use is to pass short anonymous functions as parameters to other functions, as we did above.
Anonymous functions are very common; using them is a skill that pays off to learn. In O1, we’ll be using anonymous functions extensively.
Terminological note: lambda
You may hear function literals referred to as lambda expressions
and anonymous functions referred to as lambda functions. Some
programming languages also use “lambda” as a keyword; in the Python
language, for instance, the expression lambda number: number + 1
corresponds to Scala’s number => number + 1
.
That convention has its own history. This sort of expression originates in the lambda calculus of the mathematician Alonzo Church, who had a pivotal role in the development of computer science.
As for how the Greek letter lambda ended up naming that branch of
mathematics, that happened almost literally by pulling the name from a
hat. Church didn’t write number => number + 1
but ŷ.y+1
. That is,
he used a so-called hat
on top of the parameter. However, the typesetters of the 1930s apparently
had trouble with that and yanked the hat a bit to the left, producing
^y.y+1
where it looked like Λ, a lambda.
Examples of Function Literals
Many of the examples from Chapter 6.1 can be expressed elegantly with function literals. Let’s rewrite some of them.
Example: tabulate
Here is how we used tabulate
to initialize a vector:
Vector.tabulate(10)(doubled)res9: Vector[Int] = Vector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)
We don’t need doubled
for that. We can do the same thing neatly with an anonymous
function:
Vector.tabulate(10)(index => 2 * index)
A+ presents the exercise submission form here.
Side note: making space for literals
An effortless way to make your code a little bit easier to read is to put space characters around function literals so that they stand out better from their context.
Vector.tabulate(10)( index => 2 * index )
In this ebook, we’ll frequently do this. You may also wish to adopt this convention.
Examples: areSorted
and its parameters
In Chapter 6.1, we wrote this higher-order function:
def areSorted(first: String, second: String, third: String, compare: (String, String) => Int) =
compare(first, second) <= 0 && compare(second, third) <= 0
We also defined these functions that work as parameters for areSorted
:
def compareLengths(string1: String, string2: String) = string1.length - string2.length
def compareIntContents(string1: String, string2: String) = string1.toInt - string2.toInt
def compareChars(string1: String, string2: String) = string1.compareToIgnoreCase(string2)
We used them like this:
areSorted("Java", "Scala", "Haskell", compareLengths)
areSorted("Java", "Scala", "Haskell", compareChars)
areSorted("200", "123", "1000", compareIntContents)
Now that we know about function literals, we can rewrite the three expressions above as:
areSorted("Java", "Scala", "Haskell", (s1, s2) => s1.length - s2.length )
areSorted("Java", "Scala", "Haskell", (s1, s2) => s1.compareToIgnoreCase(s2) )
areSorted("200", "123", "1000", (s1, s2) => s1.toInt - s2.toInt )
Don’t forget the brackets.
A+ presents the exercise submission form here.
Example: findAll
and a method call in a literal
In Chapter 6.1, we added a findAll
method to class AuctionHouse
. It’s given a function
that represents a search criterion and finds all items that match that criterion.
class AuctionHouse(val name: String):
private val items = Buffer[EnglishAuction]()
def findAll(checkCriterion: EnglishAuction => Boolean) =
val found = Buffer[EnglishAuction]()
for currentItem <- this.items do
if checkCriterion(currentItem) then
found += currentItem
end if
end for
found.toVector
// etc.
end AuctionHouse
This is how we used findAll
:
def checkIfOpen(candidate: EnglishAuction) = candidate.isOpen
def checkIfHandbag(candidate: EnglishAuction) = candidate.description.toLowerCase.contains("handbag")
def findAll(checkCriterion: EnglishAuction => Boolean) =
val house = AuctionHouse("ReBay")
house.addItem(EnglishAuction("A glorious handbag", 100, 14))
house.addItem(EnglishAuction("Collectible Easter Bunny China Thimble", 1, 10))
println(house.findAll(checkIfOpen)) // finds both auctions
println(house.findAll(checkIfHandbag)) // finds only the first auction
How would you implement testFindAll
with anonymous functions instead? That is, let’s
assume that we don’t have the named functions checkIfOpen
and checkIfHandbag
. How
would you rewrite the two last commands that call findAll
?
A+ presents the exercise submission form here.
Example: repeatForEachElement
A+ presents the exercise submission form here.
Example: function literals and image processing
Some more old code from 6.1:
def blueGradient(x: Int, y: Int) = Color(0, 0, x.toDouble / (size - 1) * Color.Max)blueGradient(x: Int, y: Int): Color Pic.generate(size, size, blueGradient)res10: Pic = generated pic
We can replace the above with this:
Pic.generate(size, size, (x, y) => Color(0, 0, x.toDouble / (size - 1) * Color.Max) ) res4: Pic = generated pic
A+ presents the exercise submission form here.
How about this Pic
-generating example?
def artwork(x: Int, y: Int) = if x * x > y * 100 then Red else if x + y < 200 then Black else if y % 10 < 5 then Blue else Whiteartwork(x: Int, y: Int): Color Pic.generate(size, size * 2, artwork)res11: Pic = generated pic
The longer your function literal would be, the less likely it is to improve readability. The following literal, for instance, is fairly convoluted. Splitting it across multiple lines helps somewhat, but we’re getting into territory where it would probably be better to use a named function instead.
Pic.generate(size, size * 2, (x, y) => if x * x > y * 100 then Red else if x + y < 200 then Black else if y % 10 < 5 then Blue else White )res12: Pic = generated pic
A turning point in this chapter
Now you know how to use function literals to define anonymous functions. Many but not all programming languages support anonymous functions in one way or another. Anonymous functions are used especially often in the functional programming paradigm, which we’ll say more about in Chapter 11.2.
The second half of this chapter is rather specific to Scala: you will learn another notation for writing function literals.
Anonymous Parameters
When writing a short function literal, the name of the parameter variable often isn’t too important. No matter which of the following you write, it will be obvious to the reader that you’re defining a function that returns an incremented number.
number => number + 1
x => x + 1
something => something + 1
For that reason, Scala lets you leave out the parameter name altogether and so write function literals very compactly.
Consider again the literal number => number + 1
, whose core meaning is that 1
gets added to some given value. We can write a literal that captures just that core
meaning. This tiny expression defines exactly the same anonymous function:
_ + 1
The underscore means an anonymous parameter. Since our literal contains only a single underscore, it defines a function that takes only a single parameter. One is added to that parameter’s value; the sum is this anonymous function’s return value.
As you see, this abbreviated notation doesn’t feature a right-pointing arrow at all. The underscore is enough to turn the expression into a function literal.
You can use these compact literals just like the full literals:
twice( _ + 1 , 1000) // returns 1002
twice( 2 * _ , 1000) // returns 4000
Earlier in this chapter, we listed some of the pros and cons of function literals compared to named functions. Both the pros and the cons are further emphasized in the compact “underscore literals”. If you use them judiciously, they are elegant and improve readability. At first, they may seem nearly magical, but you’ll quickly get used to them.
Annotating parameter types
You can usually leave out parameter types from abbreviated function literals just like you can usually leave them out from the full notation. We have done so in all the examples above.
You can add type annotations to abbreviated literals, too.
For instance, you could write _ + 1
as (_: Int) + 1
. However,
anonymous parameters with type annotations usually aren’t the
nicest to read, and we won’t be doing this in O1.
Multiple anonymous parameters
A function can have multiple anonymous parameters: the first underscore in the function literal refers to the first parameter, the second underscore to the second parameter, and so forth.
These two commands, for example, are equivalent:
Vector.tabulate(3, 5)( (row, column) => row + column )
Vector.tabulate(3, 5)( _ + _ )
You can similarly write these familiar function literals:
areSorted("Java", "Scala", "Haskell", (s1, s2) => s1.length - s2.length )
areSorted("Java", "Scala", "Haskell", (s1, s2) => s1.compareToIgnoreCase(s2) )
areSorted("200", "123", "1000", (s1, s2) => s1.toInt - s2.toInt )
in a shorter form:
areSorted("Java", "Scala", "Haskell", _.length - _.length )
areSorted("Java", "Scala", "Haskell", _.compareToIgnoreCase(_) )
areSorted("200", "123", "1000", _.toInt - _.toInt )
A+ presents the exercise submission form here.
A Few Words about Notations
Let’s say we have a collection of elements and we wish to print out each element in it. Here are three instructions to that end, expressed in English:
Long form: “Repeat for each element: print x, where x is the element being processed.”
Short form: “Repeat for each element: print the element.”
Very terse form: “Repeat for each element: print.”
In English, we can choose how we express ourselves based on what suits the context and how we prefer to speak or write.
Scala is pretty flexible for a programming language. Like English, it lets you express
this meaning in different ways. Assuming that we’ve defined repeatForEachElement
as in
Chapter 6.1, and a vector of integers, all of the following do the same thing:
Long form:
repeatForEachElement(myVector, x => println(x) )
Short form:
repeatForEachElement(myVector, println(_) )
Very terse form:
repeatForEachElement(myVector, println)
The latter two are essentially shorthand for the longest version.
Consider another example. Let’s assume myObj
refers to some object with an act
method that takes an Int
parameter.
Long form:
repeatForEachElement(myVector, element => myObj.act(element) )
Short form:
repeatForEachElement(myVector, myObj.act(_) )
Very terse form:
repeatForEachElement(myVector, myObj.act)
Given that all three work equally well, you can make the decision with readability in
mind. As with natural language, which one is best depends on the context and on the
reader. The very terse form says only what is required; it probably works best when the
parameter function is very familiar (like println
is). In contrast, the “short form”
is more explicit about how each value in turn becomes a parameter: this is a little more
apparent from myObj.act(_)
than from myObj.act
, which looks identical to calling a
parameterless method. The “long form” further highlights the parameter variable and lets
you name it.
You’ll see examples of all three forms in O1 and in Scala programs written by others. It’s a good idea to know them all even if you prefer one of them yourself.
A rule of thumb
You can always use the long form of function literals — the one with the arrow — until you feel like you’d like a more succinct way to write things.
Restrictions on Anonymous Parameters
A restriction: use parameters just once
Abbreviated function literals have a limitation that is revealed by the following experiment.
Here is a function that uses its parameter to compute a result. The key thing to note here is that the function body features the same parameter more than once.
def compute(number: Double) = number * number + 10
We can express the same computation as an anonymous function:
number => number * number + 10
However, this abbreviated literal fails to do the same thing:
_ * _ + 10
Since each underscore corresponds to a separate anonymous parameter, the above literal does the same as these functions:
(number1, number2) => number1 * number2 + 10
def compute(number1: Double, number2: Double) = number1 * number2 + 10
That is, the literal defines an entirely different function with two parameters.
You can use each anonymous parameter just once within the function literal: each underscore refers to a separate parameter. If you need to refer to a function literal’s parameter multiple times, use a full literal with named parameters.
Here is another example of the same restriction:
def swapGreenAndBlue(original: Color) = Color(original.red, original.blue, original.green)
The parameter variable original
appears multiple times in
the function body. It’s possible to write a function literal that
corresponds to swapGreenAndBlue
(and indeed you did so above),
but the parameter needs a name.
Another restriction: nested brackets
When you call another function within a function literal, you need to heed the following
restriction, which we’ll again explore through examples. These examples make use of the
doubled
function that we defined earlier.
First, as a point of reference, consider these two expressions that we’ve already discussed. Both increment 1000 twice to 1002:
Longer function literal |
Abbreviated literal |
Do they work? |
||
---|---|---|---|---|
|
|
Both work fine. |
How about the next example? Here we’d like to do the following twice: add one and double
the result. Doing this on 1000 should produce 4006, for instance. So we write a function
literal that calls doubled
:
Longer function literal |
Abbreviated literal |
Do they work? |
---|---|---|
|
|
The longer one works.
The shorter one doesn’t.
|
In this scenario, the compact literal doesn’t mean what we wanted. Instead, the underscore
“expands within the inner brackets”, so to speak. The literal doubled(_ + 1)
stands for
doubled(x => x + 1)
, which isn’t a valid expression. In situations like this, use the
full literal notation.
Below is a third example that does the following twice: first double the number, then add one. Doing this on 1000 should produce 4003.
Longer function literal |
Abbreviated literal |
Do they work? |
---|---|---|
|
|
Both work fine. |
When there’s nothing at all except the underscore inside the brackets, as in doubled(_)
,
the underscore “expands outside the brackets”. Which is why the compact form works in this
case.
These rules may well seem confusing at first. Don’t worry. You can always use the lengthier literal notation.
Practice on Anonymous Parameters
Summary of Key Points
Just as there are literal notations for strings, numbers, and the like, there are literal notations for functions. A function literal defines an anonymous function.
If you use them judiciously, anonymous functions can make your code easier to write and easier to read.
Anonymous functions are particularly convenient when a short function is needed in a single location only. A typical use case is to call a higher-order function and pass in an anonymous function constructed “on the fly”.
Scala has two notations for function literals. The full notation uses a
=>
arrow and names each parameter. The compact notation uses underscores_
to indicate that not only the function, but its parameters too, are anonymous.The full notation always works. The abbreviated notation works for many but not all purposes.
It’s fine to always use the full notation in your own code, but you do need to be able to read both notations.
Links to the glossary: function literal, anonymous function, anonymous parameter.
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, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, 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 appear at the ends of some chapters.
We use a function literal (funktioliteraali) that defines an anonymous function (nimetön funktio). You can read this literal as “an anonymous function that takes in a parameter
number
and returns the valuenumber + 1
”. This anonymous function is passed totwice
as its first parameter and consequently invoked two times.