How to define Scala methods that take complex functions as parameters (syntax)

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 9.4, “How to define Scala methods that take complex functions as parameters.”

Back to top

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.

Back to top

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.

Back to top

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 Ints 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)
Back to top

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.

Back to top

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.

Back to top

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations:

Back to top

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.