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”.

Consider this

Consider the following two functions to sum and subtract two integers:

 
         def sum(a: Int, b: Int): Int = a + b
  
         def subtract(a: Int, b: Int): Int = a – b
  

Which parts these two functions have in common? Which ones are not? Can you think of a more concise way of achieving the same implementation?

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

Readability first!

In future lessons and units, you’ll discover how useful and expressive the concise notation for anonymous functions is, in particular when combined with higher order functions.

Depending on the context you are in, this notation can become quite cryptic and hard to read due to all the information removed and inferred at compile time instead. For example, you could argue that the expression “_ - _” is less expressive and more confusing than “{ (a, b) => a - b }”.

Do not compromise the readability of your code. When using the concise notation for anonymous functions, use it only when the omitted information is easy to infer for both the compiler and your fellow developers.

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.