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.
Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 6.2: Anonymous Functions
About This Page
Questions Answered: What would make higher-order functions easier to work with? How can I define a function “on the fly” as I use it?
Topics: Function literals and anonymous functions. Scala’s shorthand notations for function literals.
What Will I Do? Read and work on small assignments.
Rough Estimate of Workload:? Two hours or so.
Points Available: A35 + B5 + C5.
Related Modules: HigherOrder. We’ll touch on AuctionHouse1, too.
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 something that 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:
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
We can similarly use a function literal to double a number twice:
twice(n => 2 * n, 1000)res3: Int = 4000
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, parameter types can very often be automatically inferred. 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>
Int => Int
.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 anonymous function is affixed to what you do with it. 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 expressions originate
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, it 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) {
if (checkCriterion(currentItem)) {
found += currentItem
}
}
found.toVector
}
// etc.
}
This is how we used findAll
:
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
}
How would you implement FindAllTest
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) 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)res11: Pic = generated pic
The longer your function literal would be, the less likely it is that it will improve readability. The following literal, for instance, is fairly convoluted. Splitting it across multiple lines helps somewhat, but we’re getting into the territory where it would probably be better to use a named function.
Pic.generate(size, size * 2, (x, y) => if (x * x > y * 100) Red else if (x + y < 200) Black else if (y % 10 < 5) 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 10.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 if you write number => number + 1
or x => x + 1
or
something => something + 1
, it will be obvious to the reader that you’re defining
a function that returns an incremented number.
For that reason, Scala lets you altogether leave out the parameter name and thereby write function literals very compactly.
Consider again the literal number => number + 1
, whose core meaning is that the number
one 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 emphsized 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 generally 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 as 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 the fact that each of the values in turn becomes a parameter:
this is 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 each of the 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 and the named parameters — until you feel like you’d like a more succinct way to write things.
def
and assigning functions to variables
Assume we’ve def
ined a function next
as before:
def next(n: Int) = n + 1next: (n: Int)Int
Earlier in this chapter, you saw that we can assign an anonymous
function to a variable and we can then use the variable’s name to
call the function. It would seem natural that we could similarly
assign the named function next
to a variable. To our annoyance,
we see that this doesn’t work:
val experiment = next <console>:15: error: missing argument list for method next Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing `next _` or `next(_)` instead of `next`. val experiment = next ^
The complaint is that we didn’t pass any parameters to next
.
Unlike we intended, the Scala compiler interpreted our code as an
attempt to call next
and assign the resulting integer in
experiment
(which would have a type of Int
).
The compiler additionally suggests that we might wish to use an underscore. And indeed we can accomplish what we wanted with a bit of trickery:
val experiment = next(_)experiment: Int => Int = <function1> experiment(100)res13: Int = 101
As a matter of fact, we may even omit the brackets here, making the underscore refer to the entire parameter list:
val experiment = next _experiment: Int => Int = <function1>
Where there is sufficient type information available, the underscore
isn’t required. Below, we’ve added an explicit type annotation on
the variable, so that it’s clear that we mean to assign next
to the
variable rather than calling it.
val experiment: Int => Int = nextexperiment: Int => Int = <function1>
For more information, see the book Programming in Scala.
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? | ||
---|---|---|---|---|
twice( x => x + 1 , 1000) |
twice( _ + 1 , 1000) |
Both work fine. |
How about the next example? Now we’d like to do this 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? |
---|---|---|
twice( x => doubled(x + 1) , 1000) |
twice( doubled(_ + 1) , 1000) |
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? |
---|---|---|
twice( x => doubled(x) + 1 , 1000) |
twice( doubled(_) + 1 , 1000) |
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 that has 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, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, 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 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 and Juha Sorva. 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. Markku Riekkinen is the current lead developer; 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 was created by Nikolai Denissov, Olli Kiljunen, Nikolas Drosdek, Styliani Tsovou, Jaakko Närhi, and Paweł Stróżański with input from Juha Sorva, Otto Seppälä, Arto Hellas, and others.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
number
and returns the valuenumber + 1
”. This anonymous function is passed totwice
as its first parameter and consequently invoked two times.