Table of Contents
This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 9.4, “How to define Scala methods that take complex functions as parameters.”
Problem
You want to define a Scala method that takes a function as a parameter, and that function may have one or more input parameters, and may also return a value.
Solution
Following the approach described in the previous recipe, define a method that takes a function as a parameter. Specify the function signature you expect to receive, and then execute that function inside the body of the method.
The following example defines a method named exec
that takes a function as an input parameter. That function must take one Int
as an input parameter and return nothing:
def exec(callback: Int => Unit) { // invoke the function we were given, giving it an Int parameter callback(1) }
Next, define a function that matches the expected signature. The following plusOne
function matches that signature, because it takes an Int
argument and returns nothing:
val plusOne = (i: Int) => { println(i+1) }
Now you can pass plusOne
into the exec
function:
exec(plusOne)
Because the function is called inside the method, this prints the number 2
.
Any function that matches this signature can be passed into the exec
method. To demonstrate this, define a new function named plusTen
that also takes an Int
and returns nothing:
val plusTen = (i: Int) => { println(i+10) }
Now you can pass it into your
exec function, and see that it also works:
exec(plusTen) // prints 11
Although these examples are simple, you can see the power of the technique: you can easily swap in interchangeable algorithms. As long as your function signature matches what your method expects, your algorithms can do anything you want. This is comparable to swapping out algorithms in the OOP Strategy design pattern.
Discussion
The general syntax for describing a function as a method parameter is this:
parameterName: (parameterType(s)) => returnType
Therefore, to define a function that takes a String
and returns an Int
, use one of these two signatures:
executeFunction(f:(String) => Int) // parentheses are optional when the function has only one parameter executeFunction(f:String => Int)
To define a function that takes two Int
s and returns a Boolean
, use this signature:
executeFunction(f:(Int, Int) => Boolean)
The following exec
method expects a function that takes String
, Int
, and Double
parameters and returns a Seq[String]
:
exec(f:(String, Int, Double) => Seq[String])
As shown in the Solution, if a function doesn’t return anything, declare its return type as Unit
:
exec(f:(Int) => Unit) exec(f:Int => Unit)
Passing in a function with other parameters
A function parameter is just like any other method parameter, so a method can accept other parameters in addition to a function.
The following code demonstrates this in a simple example. First, define a simple function:
val sayHello = () => println("Hello")
Next, define a method that takes this function as a parameter and also takes a second Int
parameter:
def executeXTimes(callback:() => Unit, numTimes: Int) { for (i <- 1 to numTimes) callback() }
Next, pass the function value and an Int
into the method:
scala> executeXTimes(sayHello, 3) Hello Hello Hello
Though that was a simple example, this technique can be used to pass variables into the method that can then be used by the function, inside the method body. To see how this works, create a method named executeAndPrint
that takes a function and two Int
parameters:
def executeAndPrint(f:(Int, Int) => Int, x: Int, y: Int) { val result = f(x, y) println(result) }
This method is more interesting than the previous method, because it takes the Int
parameters it’s given and passes those parameters to the function it’s given in this line of code:
val result = f(x, y)
To show how this works, create two functions that match the signature of the function that executeAndPrint
expects, a sum
function and a multiply
function:
val sum = (x: Int, y: Int) => x + y val multiply = (x: Int, y: Int) => x * y
Now you can call executeAndPrint
like this, passing in the different functions, along with two Int
parameters:
executeAndPrint(sum, 2, 9) // prints 11 executeAndPrint(multiply, 3, 9) // prints 27
This is cool, because the executeAndPrint
method doesn’t know what algorithm is actually run. All it knows is that it passes the parameters x
and y
to the function it is given and then prints the result from that function. This is similar to defining an interface in Java and then providing concrete implementations of the interface in multiple classes.
Another example
Here’s one more example of this three-step process:
// 1 - define the method def exec(callback: (Any, Any) => Unit, x: Any, y: Any) { callback(x, y) } // 2 - define a function to pass in val printTwoThings =(a: Any, b: Any) => { println(a) println(b) } // 3 - pass the function and some parameters to the method case class Person(name: String) exec(printTwoThings, "Hello", Person("Dave"))
Note that in all of the previous examples where you created functions with the val
keyword, you could have created methods, and the examples would still work. For instance, you can define printTwoThings
as a method, and exec
still works:
// 2a - define a method to pass in def printTwoThings (a: Any, b: Any) { println(a) println(b) } // 3a - pass the printTwoThings method to the exec method case class Person(name: String) exec(printTwoThings, "Hello", Person("Dave"))
Behind the scenes, there are differences between these two approaches — for instance, a function implements one of the Function0
to Function22
traits — but Scala is forgiving, and lets you pass in either a method or function, as long as the signature is correct.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |