|
From Get Programming with Scala by Daniela Sfregola After reading this article, you will be able to: § Implement anonymous functions § Code using the concise notation for anonymous functions
|
Take 37% off Get Programming with Scala by entering fccsfregola into the discount code box at checkout at manning.com.
In this article, we assume familiarity with Scala functions. We’re going to discover a new type of function called “anonymous”. Anonymous functions are functions that you can define quickly and concisely. At first, they may seem just an alternative to the standard Scala functions you might know, but you’ll soon discover that they are particularly handy when combined with another type of function, called “higher order”. The concept of anonymous function is not unique to Scala: other languages, such as Java 8+ and Python, refer to it as “lambda”.
def sum(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a – b
Function versus Anonymous Function
Suppose you have implemented a calculator program to perform the standard operations on integers (i.e., sum, subtraction, multiplication, division) together with negation. Listing 1 shows a possible implementation:
Listing 1: MyCalculator
Program
object MyCalculator { def sum(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a - b def multiply(a: Int, b: Int): Int = a * b def divide(a: Int, b: Int): Int = a / b def negate(a: Int): Int = subtract(0, a) //#A }
#A Alternatively, you can also multiply by minus one.
You can use your MyCalculator
program as follows:
import MyCalculator._ sum(3, 5) // returns 8 subtract(4, 4) // returns 0 multiply(5, 3) // returns 15 divide(6, 2) // returns 3 negate(-5) // returns 5
In Scala, every function has a type: you can represent this by combining its parameters with the arrow “=>
” and its return type. For example, let’s have another look at listing 1. The function sum
has the type (Int, Int) => Int
– this reads “Int Int to Int” because it takes two integers as parameters and returns an integer. On the other hand, the function negate
has type Int => Int
– this reads “Int to Int” – because it takes an integer as the parameter and returns an integer.
The type notation for functions reflects the syntax used to implement anonymous functions. For example, you can implement the equivalent anonymous functions for sum
and negate
as follows:
def sum(a: Int, b: Int): Int = a + b // function for sum { (a: Int, b: Int) => a + b } // anonymous function for sum def negate(a: Int): Int = subtract(0, a) // function for negate { (a: Int) => subtract(0, a) } // anonymous function for negate
When implementing anonymous functions, the function name is no longer needed, while its parameters and body stay the same. Its return type also disappears because the compiler infers it from its implementation. Have a look at figure 1 for a syntax summary of how to define anonymous functions.
Figure 1: Comparison between the syntax for a function and its corresponding anonymous function.
Listing 2 shows how to re-implement your calculation program using anonymous functions:
Listing 2: MySecondCalculator
Program
object MySecondCalculator { val sum = { (a: Int, b: Int) => a + b } val subtract = { (a: Int, b: Int) => a - b } val multiply = { (a: Int, b: Int) => a * b } val divide = { (a: Int, b: Int) => a / b } val negate = { a: Int => subtract(0, a) } //#A }
#A you can omit the round parenthesis for “a: Int” because you only have one function parameter.
In the function negate
, you can omit the parenthesis for the parameter a
: you can do this when an anonymous function accepts only one parameter. Note that you have re-used the function names in listing 1 to create values that refer to the corresponding anonymous function: this is an optional step that allows you to call the function later on. You can use your MySecondCalculator
program by calling the values you have defined and by providing parameters as if they were regular functions:
import MySecondCalculator._ sum(3, 5) subtract(4, 4) multiply(5, 3) divide(6, 2) negate(-5)
QUICK CHECK 1
What is the type for each of the values defined in listing 2? Use the REPL to validate your hypothesis.
QUICK CHECK 2
Write an anonymous function equivalent to the following function:
def hello(n: String): String = s"Hello, $n!"
Concise notation for Anonymous Functions
Scala offers a more concise notation for anonymous functions: let’s see how it works.
When transforming a function to an anonymous function its return type is no longer needed because the compiler infers its type: you can do the same for the parameter type. If you provide the compiler an explicit type for your anonymous function, it will use it to infer the type of your parameters. For example, you can refactor the anonymous functions sum
and negate
shown in listing 2 as follows:
val sum = { (a: Int, b: Int) => a + b } // before val sum: (Int, Int) => Int = { (a, b) => a + b } // after val negate = { a: Int => subtract(0, a) } // before val negate: Int => Int = { a => subtract(0, a) } // after
If your anonymous function has an an implementation that consists of a single instruction and your parameters are used in order of declaration, you can even go a step further by removing the parameters completely and replacing them with an underscore:
val sum = { (a: Int, b: Int) => a + b } // before val sum: (Int, Int) => Int = { (a, b) => a + b } // first refactoring val sum: (Int, Int) => Int = { _ + _ } // second refactoring val negate = { a: Int => subtract(0, a) } // before val negate: Int => Int = { a => subtract(0, a) } // first refactoring val negate: Int => Int = { subtract(0, _) } // second refactoring
You can now refactor your calculator program using this more concise notation:
Listing 3: MyThirdCalculator
Program
object MyThirdCalculator { val sum: (Int, Int) => Int = { _ + _ } //#A val subtract: (Int, Int) => Int = { _ - _ } //#A val multiply: (Int, Int) => Int = { _ * _ } //#A val divide: (Int, Int) => Int = { _ / _ } //#A val negate: Int => Int = subtract(0, _) //#B }
#A You could omit the curly brackets here (e.g., use “_ + _” instead of “{_ + _ }”)
#B Curly brackets omitted
QUICK CHECK 3
Are functions funcA
and funcB
equivalent? In other words, do they return the same output when receiving the same input? Why? Use the REPL to verify your hypotheses.
val funcA: (Int, Int) => Int = { (a, b) => b / a } val funcB: (Int, Int) => Int = { _ / _ }
Summary
In this article, my objective was to teach you about anonymous functions.
· You can use them to create functions on the fly and without too much boilerplate: you are going to see their full potential when you’ll learn about higher order functions.
· You have also discovered their concise notation to remove unnecessary information that the compiler infers from the function’s type.
Let’s see if you got this!
Try This
Rewrite each of the following functions as anonymous functions: use your concise notation at your discretion.
1. def multiply(s: String, n: Int): Int = s.length * n 2. def toDouble(n: Int): Double = n.toDouble 3. def concat(s1: String, s2: String): String = s1 + s2 4. def inverseConcat(s1: String, s2: String): String = s2 + s1 5. def myLongFunc(s: String): String = { val length = s.length s.reverse * length }
Answers to Quick Checks
QUICK CHECK 1
The values sum
, subtract
, multiply
, divide
have the type (Int, Int) => Int
, while the value negate
has type Int => Int
.
QUICK CHECK 2
An implementation of an anonymous function equivalent to the function hello
is the following:
{ n: String => s"Hello, $n!" }
QUICK CHECK 3
Functions funcA
and funcB
are not equivalent because of the order of their parameters. Function funcA
divides its second parameter called b
by its first parameter called a
. Function funcB
does the inverse: it divides its first parameter by its second one because the compiler substitutes them by following their declaration order.
That’s all for this article. If you want to learn more about the book, you can check it out on Manning’s browser-based liveBook platform here.