ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3494
Скачиваний: 0
Section 8.2 |
Chapter 8 · Functions and Closures |
186 |
So far, this is very similar to what you would do in any object-oriented language. However, the concept of a function in Scala is more general than a method. Scala’s other ways to express functions will be explained in the following sections.
8.2Local functions
The construction of the processFile method in the previous section demonstrated an important design principle of the functional programming style: programs should be decomposed into many small functions that each do a well-defined task. Individual functions are often quite small. The advantage of this style is that it gives a programmer many building blocks that can be flexibly composed to do more difficult things. Each building block should be simple enough to be understood individually.
One problem with this approach is that all the helper function names can pollute the program namespace. In the interpreter this is not so much of a problem, but once functions are packaged in reusable classes and objects, it’s desirable to hide the helper functions from clients of a class. They often do not make sense individually, and you often want to keep enough flexibility to delete the helper functions if you later rewrite the class a different way.
In Java, your main tool for this purpose is the private method. This private-method approach works in Scala as well, as is demonstrated in Listing 8.1, but Scala offers an additional approach: you can define functions inside other functions. Just like local variables, such local functions are visible only in their enclosing block. Here’s an example:
def processFile(filename: String, width: Int) {
def processLine(filename: String, width: Int, line: String) {
if (line.length > width) println(filename +": "+ line)
}
val source = Source.fromFile(filename) for (line <- source.getLines()) {
processLine(filename, width, line)
}
}
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.2 |
Chapter 8 · Functions and Closures |
187 |
In this example, we refactored the original LongLines version, shown in Listing 8.1, by transforming private method, processLine, into a local function of processFile. To do so we removed the private modifier, which can only be applied (and is only needed) for methods, and placed the definition of processLine inside the definition of processFile. As a local function, processLine is in scope inside processFile, but inaccessible outside.
Now that processLine is defined inside processFile, however, another improvement becomes possible. Notice how filename and width are passed unchanged into the helper function? This is not necessary, because local functions can access the parameters of their enclosing function. You can just use the parameters of the outer processLine function, as shown in Listing 8.2:
import scala.io.Source
object LongLines {
def processFile(filename: String, width: Int) {
def processLine(line: String) { if (line.length > width)
println(filename +": "+ line)
}
val source = Source.fromFile(filename) for (line <- source.getLines())
processLine(line)
}
}
Listing 8.2 · LongLines with a local processLine function.
Simpler, isn’t it? This use of an enclosing function’s parameters is a common and useful example of the general nesting Scala provides. The nesting and scoping described in Section 7.7 applies to all Scala constructs, including functions. It’s a simple principle, but very powerful, especially in a language with first-class functions.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.3 |
Chapter 8 · Functions and Closures |
188 |
8.3First-class functions
Scala has first-class functions. Not only can you define functions and call them, but you can write down functions as unnamed literals and then pass them around as values. We introduced function literals in Chapter 2 and showed the basic syntax in Figure 2.2 on page 79.
A function literal is compiled into a class that when instantiated at runtime is a function value.2 Thus the distinction between function literals and values is that function literals exist in the source code, whereas function values exist as objects at runtime. The distinction is much like that between classes (source code) and objects (runtime).
Here is a simple example of a function literal that adds one to a number:
(x: Int) => x + 1
The => designates that this function converts the thing on the left (any integer x) to the thing on the right (x + 1). So, this is a function mapping any integer x to x + 1.
Function values are objects, so you can store them in variables if you like. They are functions, too, so you can invoke them using the usual parentheses function-call notation. Here is an example of both activities:
scala> var increase = (x: Int) => x + 1 increase: (Int) => Int = <function1>
scala> increase(10) res0: Int = 11
Because increase, in this example, is a var, you can reassign it a different function value later on.
scala> increase = (x: Int) => x + 9999 increase: (Int) => Int = <function1>
scala> increase(10) res1: Int = 10009
2Every function value is an instance of some class that extends one of several FunctionN traits in package scala, such as Function0 for functions with no parameters, Function1 for functions with one parameter, and so on. Each FunctionN trait has an apply method used to invoke the function.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.3 |
Chapter 8 · Functions and Closures |
189 |
If you want to have more than one statement in the function literal, surround its body by curly braces and put one statement per line, thus forming a block. Just like a method, when the function value is invoked, all of the statements will be executed, and the value returned from the function is whatever the expression on the last line generates.
scala> increase = (x: Int) => { println("We") println("are") println("here!")
x + 1
}
increase: (Int) => Int = <function1>
scala> increase(10) We
are here!
res2: Int = 11
So now you have seen the nuts and bolts of function literals and function values. Many Scala libraries give you opportunities to use them. For example, a foreach method is available for all collections.3 It takes a function as an argument and invokes that function on each of its elements. Here is how it can be used to print out all of the elements of a list:
scala> val someNumbers = List(-11, -10, -5, 0, 5, 10) someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10)
scala> someNumbers.foreach((x: Int) => println(x)) -11 -10 -5 0 5 10
As another example, collection types also have a filter method. This method selects those elements of a collection that pass a test the user sup-
3A foreach method is defined in trait Traversable, a common supertrait of List, Set, Array, and Map. See Chapter 17 for the details.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.4 |
Chapter 8 · Functions and Closures |
190 |
plies. That test is supplied using a function. For example, the function (x: Int) => x > 0 could be used for filtering. This function maps positive integers to true and all others to false. Here is how to use it with filter:
scala> someNumbers.filter((x: Int) => x > 0) res4: List[Int] = List(5, 10)
Methods like foreach and filter are described further later in the book. Chapter 16 talks about their use in class List. Chapter 17 discusses their use with other collection types.
8.4Short forms of function literals
Scala provides a number of ways to leave out redundant information and write function literals more briefly. Keep your eyes open for these opportunities, because they allow you to remove clutter from your code.
One way to make a function literal more brief is to leave off the parameter types. Thus, the previous example with filter could be written like this:
scala> someNumbers.filter((x) => x > 0) res5: List[Int] = List(5, 10)
The Scala compiler knows that x must be an integer, because it sees that you are immediately using the function to filter a list of integers (referred to by someNumbers). This is called target typing, because the targeted usage of an expression—in this case an argument to someNumbers.filter()—is allowed to influence the typing of that expression—in this case to determine the type of the x parameter. The precise details of target typing are not important to study. You can simply start by writing a function literal without the argument type, and, if the compiler gets confused, add in the type. Over time you’ll get a feel for which situations the compiler can and cannot puzzle out.
A second way to remove useless characters is to leave out parentheses around a parameter whose type is inferred. In the previous example, the parentheses around x are unnecessary:
scala> someNumbers.filter(x => x > 0) res6: List[Int] = List(5, 10)
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.5 |
Chapter 8 · Functions and Closures |
191 |
8.5Placeholder syntax
To make a function literal even more concise, you can use underscores as placeholders for one or more parameters, so long as each parameter appears only one time within the function literal. For example, _ > 0 is very short notation for a function that checks whether a value is greater than zero:
scala> someNumbers.filter(_ > 0) res7: List[Int] = List(5, 10)
You can think of the underscore as a “blank” in the expression that needs to be “filled in.” This blank will be filled in with an argument to the function each time the function is invoked. For example, given that someNumbers was initialized on page 189 to the value List(-11, -10, -5, 0, 5, 10), the filter method will replace the blank in _ > 0 first with -11, as in -11 > 0, then with -10, as in -10 > 0, then with -5, as in -5 > 0, and so on to the end of the List. The function literal _ > 0, therefore, is equivalent to the slightly more verbose x => x > 0, as demonstrated here:
scala> someNumbers.filter(x => x > 0) res8: List[Int] = List(5, 10)
Sometimes when you use underscores as placeholders for parameters, the compiler might not have enough information to infer missing parameter types. For example, suppose you write _ + _ by itself:
scala> val f = _ + _
<console>:4: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
val f = _ + _
ˆ
In such cases, you can specify the types using a colon, like this:
scala> val f = (_: Int) + (_: Int) f: (Int, Int) => Int = <function2>
scala> f(5, 10) res9: Int = 15
Note that _ + _ expands into a literal for a function that takes two parameters. This is why you can use this short form only if each parameter appears
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.6 |
Chapter 8 · Functions and Closures |
192 |
in the function literal at most once. Multiple underscores mean multiple parameters, not reuse of a single parameter repeatedly. The first underscore represents the first parameter, the second underscore the second parameter, the third underscore the third parameter, and so on.
8.6Partially applied functions
Although the previous examples substitute underscores in place of individual parameters, you can also replace an entire parameter list with an underscore. For example, rather than writing println(_), you could write println _. Here’s an example:
someNumbers.foreach(println _)
Scala treats this short form exactly as if you had written the following:
someNumbers.foreach(x => println(x))
Thus, the underscore in this case is not a placeholder for a single parameter. It is a placeholder for an entire parameter list. Remember that you need to leave a space between the function name and the underscore, because otherwise the compiler will think you are referring to a different symbol, such as for example, a method named println_, which likely does not exist.
When you use an underscore in this way, you are writing a partially applied function. In Scala, when you invoke a function, passing in any needed arguments, you apply that function to the arguments. For example, given the following function:
scala> def sum(a: Int, b: Int, c: Int) = a + b + c sum: (a: Int,b: Int,c: Int)Int
You could apply the function sum to the arguments 1, 2, and 3 like this:
scala> sum(1, 2, 3) res10: Int = 6
A partially applied function is an expression in which you don’t supply all of the arguments needed by the function. Instead, you supply some, or none, of the needed arguments. For example, to create a partially applied function expression involving sum, in which you supply none of the three required
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 8.6 |
Chapter 8 · Functions and Closures |
193 |
arguments, you just place an underscore after “sum”. The resulting function can then be stored in a variable. Here’s an example:
scala> val a = sum _
a: (Int, Int, Int) => Int = <function3>
Given this code, the Scala compiler instantiates a function value that takes the three integer parameters missing from the partially applied function expression, sum _, and assigns a reference to that new function value to the variable a. When you apply three arguments to this new function value, it will turn around and invoke sum, passing in those same three arguments:
scala> a(1, 2, 3) res11: Int = 6
Here’s what just happened: The variable named a refers to a function value object. This function value is an instance of a class generated automatically by the Scala compiler from sum _, the partially applied function expression. The class generated by the compiler has an apply method that takes three arguments.4 The generated class’s apply method takes three arguments because three is the number of arguments missing in the sum _ expression. The Scala compiler translates the expression a(1, 2, 3) into an invocation of the function value’s apply method, passing in the three arguments 1, 2, and 3. Thus, a(1, 2, 3) is a short form for:
scala> a.apply(1, 2, 3) res12: Int = 6
This apply method, defined in the class generated automatically by the Scala compiler from the expression sum _, simply forwards those three missing parameters to sum, and returns the result. In this case apply invokes sum(1, 2, 3), and returns what sum returns, which is 6.
Another way to think about this kind of expression, in which an underscore is used to represent an entire parameter list, is as a way to transform a def into a function value. For example, if you have a local function, such as sum(a: Int, b: Int, c: Int): Int, you can “wrap” it in a function value whose apply method has the same parameter list and result types. When you apply this function value to some arguments, it in turn applies sum to
4The generated class extends trait Function3, which declares a three-arg apply method.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index