ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 3488

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Section 7.8

Chapter 7 · Built-in Control Structures

181

In the interpreter, you can reuse variable names to your heart’s content. Among other things, this allows you to change your mind if you made a mistake when you defined a variable the first time in the interpreter. The reason you can do this is that, conceptually, the interpreter creates a new nested scope for each new statement you type in. Thus, you could visualize the previous interpreted code like this:

val a = 1;

{

val a = 2;

{

println(a)

}

}

This code will compile and run as a Scala script, and like the code typed into the interpreter, will print 2. Keep in mind that such code can be very confusing to readers, because variable names adopt new meanings in nested scopes. It is usually better to choose a new, meaningful variable name rather than to shadow an outer variable.

7.8Refactoring imperative-style code

To help you gain insight into the functional style, in this section we’ll refactor the imperative approach to printing a multiplication table shown in Listing 7.18. Our functional alternative is shown in Listing 7.19.

The imperative style reveals itself in Listing 7.18 in two ways. First, invoking printMultiTable has a side effect: printing a multiplication table to the standard output. In Listing 7.19, we refactored the function so that it returns the multiplication table as a string. Since the function no longer prints, we renamed it multiTable. As mentioned previously, one advantage of side-effect-free functions is they are easier to unit test. To test printMultiTable, you would need to somehow redefine print and println so you could check the output for correctness. You could test multiTable more easily, by checking its string result.

The other telltale sign of the imperative style in printMultiTable is its while loop and vars. By contrast, the multiTable function uses vals, for expressions, helper functions, and calls to mkString.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 7.8

Chapter 7 · Built-in Control Structures

182

//Returns a row as a sequence def makeRowSeq(row: Int) =

for (col <- 1 to 10) yield {

val prod = (row * col).toString

val padding = " " * (4 - prod.length) padding + prod

}

//Returns a row as a string

def makeRow(row: Int) = makeRowSeq(row).mkString

// Returns table as a string with one row per line def multiTable() = {

val tableSeq = // a sequence of row strings for (row <- 1 to 10)

yield makeRow(row)

tableSeq.mkString("\n")

}

Listing 7.19 · A functional way to create a multiplication table.

We factored out the two helper functions, makeRow and makeRowSeq, to make the code easier to read. Function makeRowSeq uses a for expression whose generator iterates through column numbers 1 through 10. The body of this for calculates the product of row and column, determines the padding needed for the product, and yields the result of concatenating the padding and product strings. The result of the for expression will be a sequence (some subclass of scala.Seq) containing these yielded strings as elements. The other helper function, makeRow, simply invokes mkString on the result returned by makeRowSeq. mkString will concatenate the strings in the sequence and return them as one string.

The multiTable method first initializes tableSeq with the result of a for expression whose generator iterates through row numbers 1 to 10, and for each calls makeRow to get the string for that row. This string is yielded, thus the result of this for expression will be a sequence of row strings. The only remaining task is to convert the sequence of strings into a single string. The call to mkString accomplishes this, and because we pass "\n", we get an end of line character inserted between each string. If you pass the string

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Section 7.9

Chapter 7 · Built-in Control Structures

183

returned by multiTable to println, you’ll see the same output that’s produced by calling printMultiTable:

1

2

3

4

5

6

7

8

9

10

2

4

6

8

10

12

14

16

18

20

3

6

9

12

15

18

21

24

27

30

4

8

12

16

20

24

28

32

36

40

5

10

15

20

25

30

35

40

45

50

6

12

18

24

30

36

42

48

54

60

7

14

21

28

35

42

49

56

63

70

8

16

24

32

40

48

56

64

72

80

9

18

27

36

45

54

63

72

81

90

10

20

30

40

50

60

70

80

90

100

7.9Conclusion

Scala’s built-in control structures are minimal, but they do the job. They act much like their imperative equivalents, but because they tend to result in a value, they support a functional style, too. Just as important, they are careful in what they omit, thus leaving room for one of Scala’s most powerful features, the function literal, which will be described in the next chapter.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Chapter 8

Functions and Closures

When programs get larger, you need some way to divide them into smaller, more manageable pieces. For dividing up control flow, Scala offers an approach familiar to all experienced programmers: divide the code into functions. In fact, Scala offers several ways to define functions that are not present in Java. Besides methods, which are functions that are members of some object, there are also functions nested within functions, function literals, and function values. This chapter takes you on a tour through all of these flavors of functions in Scala.

8.1Methods

The most common way to define a function is as a member of some object. Such a function is called a method. As an example, Listing 8.1 shows two methods that together read a file with a given name and print out all lines whose length exceeds a given width. Every printed line is prefixed with the name of the file it appears in.

The processFile method takes a filename and width as parameters. It creates a Source object from the file name and, in the generator of the for expression, calls getLines on the source. As mentioned in Step 12 of Chapter 3, getLines returns an iterator that provides one line from the file on each iteration, excluding the end-of-line character. The for expression processes each of these lines by calling the helper method, processLine. The processLine method takes three parameters: a filename, a width, and a line. It tests whether the length of the line is greater than the given width, and, if so, it prints the filename, a colon, and the line.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 8.1

Chapter 8 · Functions and Closures

185

import scala.io.Source

object LongLines {

def processFile(filename: String, width: Int) { val source = Source.fromFile(filename)

for (line <- source.getLines()) processLine(filename, width, line)

}

private def processLine(filename: String, width: Int, line: String) {

if (line.length > width) println(filename +": "+ line.trim)

}

}

Listing 8.1 · LongLines with a private processLine method.

To use LongLines from the command line, we’ll create an application that expects the line width as the first command-line argument, and interprets subsequent arguments as filenames:1

object FindLongLines {

def main(args: Array[String]) { val width = args(0).toInt for (arg <- args.drop(1))

LongLines.processFile(arg, width)

}

}

Here’s how you’d use this application to find the lines in LongLines.scala that are over 45 characters in length (there’s just one):

$ scala FindLongLines 45 LongLines.scala

LongLines.scala: def processFile(filename: String, width: Int) {

1In this book, we usually won’t check command-line arguments for validity in example applications, both to save trees and reduce boilerplate code that can obscure the example’s important code. The trade-off is that instead of producing a helpful error message when given bad input, our example applications will throw an exception.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index