ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3485
Скачиваний: 0
Step 11 |
Chapter 3 · Next Steps in Scala |
98 |
anything out like the printArgs methods did, but you can easily pass its result to println to accomplish that:
println(formatArgs(args))
Every useful program is likely to have side effects of some form, because otherwise it wouldn’t be able to provide value to the outside world. Preferring methods without side effects encourages you to design programs where side-effecting code is minimized. One benefit of this approach is that it can help make your programs easier to test. For example, to test any of the three printArgs methods shown earlier in this section, you’d need to redefine println, capture the output passed to it, and make sure it is what you expect. By contrast, you could test the formatArgs function simply by checking its result:
val res = formatArgs(Array("zero", "one", "two")) assert(res == "zero\none\ntwo")
Scala’s assert method checks the passed Boolean and if it is false, throws AssertionError. If the passed Boolean is true, assert just returns quietly. You’ll learn more about assertions and testing in Chapter 14.
That said, bear in mind that neither vars nor side effects are inherently evil. Scala is not a pure functional language that forces you to program everything in the functional style. Scala is a hybrid imperative/functional language. You may find that in some situations an imperative style is a better fit for the problem at hand, and in such cases you should not hesitate to use it. To help you learn how to program without vars, however, we’ll show you many specific examples of code with vars and how to transform those vars to vals in Chapter 7.
A balanced attitude for Scala programmers
Prefer vals, immutable objects, and methods without side effects. Reach for them first. Use vars, mutable objects, and methods with side effects when you have a specific need and justification for them.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 12 |
Chapter 3 · Next Steps in Scala |
99 |
Step 12. Read lines from a file
Scripts that perform small, everyday tasks often need to process lines in files. In this section, you’ll build a script that reads lines from a file and prints them out prepended with the number of characters in each line. The first version is shown in Listing 3.10:
import scala.io.Source
if (args.length > 0) {
for (line <- Source.fromFile(args(0)).getLines()) println(line.length +" "+ line)
}
else
Console.err.println("Please enter filename")
Listing 3.10 · Reading lines from a file.
This script starts with an import of a class named Source from package scala.io. It then checks to see if at least one argument was specified on the command line. If so, the first argument is interpreted as a filename to open and process. The expression Source.fromFile(args(0)) attempts to open the specified file and returns a Source object, on which you call getLines. The getLines method returns an Iterator[String], which provides one line on each iteration, excluding the end-of-line character. The for expression iterates through these lines and prints for each the length of the line, a space, and the line itself. If there were no arguments supplied on the command line, the final else clause will print a message to the standard error stream. If you place this code in a file named countchars1.scala, and run it on itself with:
$ scala countchars1.scala countchars1.scala
You should see:
22 import scala.io.Source
0
22 if (args.length > 0) {
0
51 for (line <- Source.fromFile(args(0)).getLines())
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 12 |
Chapter 3 · Next Steps in Scala |
100 |
35 println(line.length +" "+ line)
1 }
4 else
46 Console.err.println("Please enter filename")
Although the script in its current form prints out the needed information, you may wish to line up the numbers, right adjusted, and add a pipe character, so that the output looks instead like:
22 | import scala.io.Source
0 |
22 | if (args.length > 0) {
0 |
51 | for (line <- Source.fromFile(args(0)).getLines())
35 | println(line.length +" "+ line)
1 | }
4 | else
46 | Console.err.println("Please enter filename")
To accomplish this, you can iterate through the lines twice. The first time through you’ll determine the maximum width required by any line’s character count. The second time through you’ll print the output, using the maximum width calculated previously. Because you’ll be iterating through the lines twice, you may as well assign them to a variable:
val lines = Source.fromFile(args(0)).getLines().toList
The final toList is required because the getLines method returns an iterator. Once you’ve iterated through an iterator, it is spent. By transforming it into a list via the toList call, you gain the ability to iterate as many times as you wish, at the cost of storing all lines from the file in memory at once. The lines variable, therefore, references a list of strings that contains the contents of the file specified on the command line.
Next, because you’ll be calculating the width of each line’s character count twice, once per iteration, you might factor that expression out into a small function, which calculates the character width of the passed string’s length:
def widthOfLength(s: String) = s.length.toString.length
With this function, you could calculate the maximum width like this:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 12 |
Chapter 3 · Next Steps in Scala |
101 |
var maxWidth = 0 for (line <- lines)
maxWidth = maxWidth.max(widthOfLength(line))
Here you iterate through each line with a for expression, calculate the character width of that line’s length, and, if it is larger than the current maximum, assign it to maxWidth, a var that was initialized to 0. (The max method, which you can invoke on any Int, returns the greater of the value on which it was invoked and the value passed to it.) Alternatively, if you prefer to find the maximum without vars, you could first find the longest line like this:
val longestLine = lines.reduceLeft(
(a, b) => if (a.length > b.length) a else b
)
The reduceLeft method applies the passed function to the first two elements in lines, then applies it to the result of the first application and the next element in lines, and so on, all the way through the list. On each such application, the result will be the longest line encountered so far, because the passed function, (a, b) => if (a.length > b.length) a else b, returns the longest of the two passed strings. “reduceLeft” will return the result of the last application of the function, which in this case will be the longest string element contained in lines.
Given this result, you can calculate the maximum width by passing the longest line to widthOfLength:
val maxWidth = widthOfLength(longestLine)
All that remains is to print out the lines with proper formatting. You can do that like this:
for (line <- lines) {
val numSpaces = maxWidth - widthOfLength(line) val padding = " " * numSpaces
println(padding + line.length +" | "+ line)
}
In this for expression, you once again iterate through the lines. For each line, you first calculate the number of spaces required before the line length and assign it to numSpaces. Then you create a string containing numSpaces
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Conclusion |
Chapter 3 · Next Steps in Scala |
102 |
spaces with the expression " " * numSpaces. Finally, you print out the information with the desired formatting. The entire script looks as shown in Listing 3.11:
import scala.io.Source
def widthOfLength(s: String) = s.length.toString.length
if (args.length > 0) {
val lines = Source.fromFile(args(0)).getLines().toList
val longestLine = lines.reduceLeft(
(a, b) => if (a.length > b.length) a else b
)
val maxWidth = widthOfLength(longestLine)
for (line <- lines) {
val numSpaces = maxWidth - widthOfLength(line) val padding = " " * numSpaces
println(padding + line.length +" | "+ line)
}
}
else
Console.err.println("Please enter filename")
Listing 3.11 · Printing formatted character counts for the lines of a file.
Conclusion
With the knowledge you’ve gained in this chapter, you should already be able to get started using Scala for small tasks, especially scripts. In future chapters, we will dive into more detail in these topics, and introduce other topics that weren’t even hinted at here.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Chapter 4
Classes and Objects
You’ve already seen the basics of classes and objects in Scala in the previous two chapters. In this chapter, we’ll take you a bit deeper. You’ll learn more about classes, fields, and methods, and get an overview of semicolon inference. You’ll learn more about singleton objects, including how to use them to write and run a Scala application. If you are familiar with Java, you’ll find the concepts in Scala are similar, but not exactly the same. So even if you’re a Java guru, it will pay to read on.
4.1Classes, fields, and methods
A class is a blueprint for objects. Once you define a class, you can create objects from the class blueprint with the keyword new. For example, given the class definition:
class ChecksumAccumulator {
// class definition goes here
}
You can create ChecksumAccumulator objects with:
new ChecksumAccumulator
Inside a class definition, you place fields and methods, which are collectively called members. Fields, which you define with either val or var, are variables that refer to objects. Methods, which you define with def, contain executable code. The fields hold the state, or data, of an object, whereas the methods use that data to do the computational work of the object. When you
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 4.1 |
Chapter 4 · Classes and Objects |
104 |
instantiate a class, the runtime sets aside some memory to hold the image of that object’s state—i.e., the content of its variables. For example, if you defined a ChecksumAccumulator class and gave it a var field named sum:
class ChecksumAccumulator { var sum = 0
}
and you instantiated it twice with:
val acc = new ChecksumAccumulator val csa = new ChecksumAccumulator
The image of the objects in memory might look like:
sum
acc
0
sum
csa
Since sum, a field declared inside class ChecksumAccumulator, is a var, not a val, you can later reassign to sum a different Int value, like this:
acc.sum = 3
Now the picture would look like:
sum
acc
sum
csa
3
0
One thing to notice about this picture is that there are two sum variables, one in the object referenced by acc and the other in the object referenced
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index