ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3503
Скачиваний: 0
Step 8 |
Chapter 3 · Next Steps in Scala |
89 |
Table 3.1 · continued
thrill.foreach(print)
thrill.head
thrill.init
thrill.isEmpty
thrill.last
thrill.length
thrill.map(s => s + "y")
thrill.mkString(", ")
thrill.remove(s => s.length == 4)
thrill.reverse
thrill.sort((s, t) => s.charAt(0).toLower <
t.charAt(0).toLower)
thrill.tail
Same as the previous, but more concise (also prints "Willfilluntil")
Returns the first element in the thrill list (returns "Will")
Returns a list of all but the last element in the thrill list (returns
List("Will", "fill"))
Indicates whether the thrill list is empty (returns false)
Returns the last element in the thrill list (returns "until")
Returns the number of elements in the thrill list (returns 3)
Returns a list resulting from adding a "y" to each string element in the thrill list (returns
List("Willy", "filly", "untily"))
Makes a string with the elements of the list (returns "Will, fill, until")
Returns a list of all elements, in order, of the thrill list except those that have length 4 (returns List("until"))
Returns a list containing all elements of the thrill list in reverse order (returns
List("until", "fill", "Will"))
Returns a list containing all elements of the thrill list in alphabetical order of the first character lowercased (returns
List("fill", "until", "Will"))
Returns the thrill list minus its first element (returns
List("fill", "until"))
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 9 |
Chapter 3 · Next Steps in Scala |
90 |
Step 9. Use tuples
Another useful container object is the tuple. Like lists, tuples are immutable, but unlike lists, tuples can contain different types of elements. Whereas a list might be a List[Int] or a List[String], a tuple could contain both an integer and a string at the same time. Tuples are very useful, for example, if you need to return multiple objects from a method. Whereas in Java you would often create a JavaBean-like class to hold the multiple return values, in Scala you can simply return a tuple. And it is simple: to instantiate a new tuple that holds some objects, just place the objects in parentheses, separated by commas. Once you have a tuple instantiated, you can access its elements individually with a dot, underscore, and the one-based index of the element. An example is shown in Listing 3.4:
val pair = (99, "Luftballons") println(pair._1) println(pair._2)
Listing 3.4 · Creating and using a tuple.
In the first line of Listing 3.4, you create a new tuple that contains the integer 99, as its first element, and the string, "Luftballons", as its second element. Scala infers the type of the tuple to be Tuple2[Int, String], and gives that type to the variable pair as well. In the second line, you access the _1 field, which will produce the first element, 99. The “.” in the second line is the same dot you’d use to access a field or invoke a method. In this case you are accessing a field named _1. If you run this script, you’ll see:
99 Luftballons
The actual type of a tuple depends on the number of elements it contains and the types of those elements. Thus, the type of (99, "Luftballons") is Tuple2[Int, String]. The type of ('u', 'r', "the", 1, 4, "me") is Tuple6[Char, Char, String, Int, Int, String].5
5Although conceptually you could create tuples of any length, currently the Scala library only defines them up to Tuple22.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 10 |
Chapter 3 · Next Steps in Scala |
91 |
Accessing the elements of a tuple
You may be wondering why you can’t access the elements of a tuple like the elements of a list, for example, with “pair(0)”. The reason is that a list’s apply method always returns the same type, but each element of a tuple may be a different type: _1 can have one result type, _2 another, and so on. These _N numbers are one-based, instead of zero-based, because starting with 1 is a tradition set by other languages with statically typed tuples, such as Haskell and ML.
Step 10. Use sets and maps
Because Scala aims to help you take advantage of both functional and imperative styles, its collections libraries make a point to differentiate between mutable and immutable collections. For example, arrays are always mutable; lists are always immutable. Scala also provides mutable and immutable alternatives for sets and maps, but uses the same simple names for both versions. For sets and maps, Scala models mutability in the class hierarchy.
For example, the Scala API contains a base trait for sets, where a trait is similar to a Java interface. (You’ll find out more about traits in Chapter 12.) Scala then provides two subtraits, one for mutable sets and another for immutable sets. As you can see in Figure 3.2, these three traits all share the same simple name, Set. Their fully qualified names differ, however, because each resides in a different package. Concrete set classes in the Scala API, such as the HashSet classes shown in Figure 3.2, extend either the mutable or immutable Set trait. (Although in Java you “implement” interfaces, in Scala you “extend” or “mix in” traits.) Thus, if you want to use a HashSet, you can choose between mutable and immutable varieties depending upon your needs. The default way to create a set is shown in Listing 3.5:
var jetSet = Set("Boeing", "Airbus") jetSet += "Lear" println(jetSet.contains("Cessna"))
Listing 3.5 · Creating, initializing, and using an immutable set.
In the first line of code in Listing 3.5, you define a new var named
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 10 |
Chapter 3 · Next Steps in Scala |
92 |
|
|
|
|
|
scala.collection
Set
«trait»
|
|
|
|
|
scala.collection.immutable |
|
scala.collection.mutable |
||
Set |
|
Set |
||
«trait» |
|
«trait» |
||
|
|
|
|
|
scala.collection.immutable scala.collection.mutable
HashSet HashSet
Figure 3.2 · Class hierarchy for Scala sets.
jetSet, and initialize it with an immutable set containing the two strings, "Boeing" and "Airbus". As this example shows, you can create sets in Scala similarly to how you create lists and arrays: by invoking a factory method named apply on a Set companion object. In Listing 3.5, you invoke apply on the companion object for scala.collection.immutable.Set, which returns an instance of a default, immutable Set. The Scala compiler infers jetSet’s type to be the immutable Set[String].
To add a new element to a set, you call + on the set, passing in the new element. Both mutable and immutable sets offer a + method, but their behavior differs. Whereas a mutable set will add the element to itself, an immutable set will create and return a new set with the element added. In Listing 3.5, you’re working with an immutable set, thus the + invocation will yield a brand new set. Although mutable sets offer an actual += method, immutable sets do not. In this case, the second line of code, “jetSet += "Lear"”, is essentially a shorthand for:
jetSet = jetSet + "Lear"
Thus, in the second line of Listing 3.5, you reassign the jetSet var with a new set containing "Boeing", "Airbus", and "Lear". Finally, the last line
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 10 |
Chapter 3 · Next Steps in Scala |
93 |
of Listing 3.5 prints out whether or not the set contains the string "Cessna". (As you’d expect, it prints false.)
If you want a mutable set, you’ll need to use an import, as shown in Listing 3.6:
import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist") movieSet += "Shrek"
println(movieSet)
Listing 3.6 · Creating, initializing, and using a mutable set.
In the first line of Listing 3.6 you import the mutable Set. As with Java, an import statement allows you to use a simple name, such as Set, instead of the longer, fully qualified name. As a result, when you say Set on the third line, the compiler knows you mean scala.collection.mutable.Set. On that line, you initialize movieSet with a new mutable set that contains the strings "Hitch" and "Poltergeist". The subsequent line adds "Shrek" to the mutable set by calling the += method on the set, passing in the string "Shrek". As mentioned previously, += is an actual method defined on mutable sets. Had you wanted to, instead of writing movieSet += "Shrek", therefore, you could have written movieSet.+=("Shrek").6
Although the default set implementations produced by the mutable and immutable Set factory methods shown thus far will likely be sufficient for most situations, occasionally you may want an explicit set class. Fortunately, the syntax is similar. Simply import that class you need, and use the factory method on its companion object. For example, if you need an immutable HashSet, you could do this:
import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies") println(hashSet + "Coriander")
Another useful collection class in Scala is Map. As with sets, Scala provides mutable and immutable versions of Map, using a class hierarchy. As
6Because the set in Listing 3.6 is mutable, there is no need to reassign movieSet, which is why it can be a val. By contrast, using += with the immutable set in Listing 3.5 required reassigning jetSet, which is why it must be a var.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 10 |
Chapter 3 · Next Steps in Scala |
94 |
|
|
|
|
|
scala.collection
Map
«trait»
|
|
|
|
|
scala.collection.immutable |
|
scala.collection.mutable |
||
Map |
|
Map |
||
«trait» |
|
«trait» |
||
|
|
|
|
|
scala.collection.immutable scala.collection.mutable
HashMap HashMap
Figure 3.3 · Class hierarchy for Scala maps.
you can see in Figure 3.3, the class hierarchy for maps looks a lot like the one for sets. There’s a base Map trait in package scala.collection, and two subtrait Maps: a mutable Map in scala.collection.mutable and an immutable one in scala.collection.immutable.
Implementations of Map, such as the HashMaps shown in the class hierarchy in Figure 3.3, extend either the mutable or immutable trait. You can create and initialize maps using factory methods similar to those used for arrays, lists, and sets. For example, Listing 3.7 shows a mutable map in action.
import scala.collection.mutable.Map
val treasureMap = Map[Int, String]() treasureMap += (1 -> "Go to island.") treasureMap += (2 -> "Find big X on ground.") treasureMap += (3 -> "Dig.") println(treasureMap(2))
Listing 3.7 · Creating, initializing, and using a mutable map.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 10 |
Chapter 3 · Next Steps in Scala |
95 |
On the first line of Listing 3.7, you import the mutable Map. You then define a val named treasureMap and initialize it with an empty mutable Map that has integer keys and string values. The map is empty because you pass nothing to the factory method (the parentheses in “Map[Int, String]()” are empty).7 On the next three lines you add key/value pairs to the map using the -> and += methods. As illustrated previously, the Scala compiler transforms a binary operation expression like 1 -> "Go to island." into
(1).->("Go to island."). Thus, when you say 1 -> "Go to island.", you are actually calling a method named -> on an integer with the value 1, passing in a string with the value "Go to island." This -> method, which you can invoke on any object in a Scala program, returns a two-element tuple containing the key and value.8 You then pass this tuple to the += method of the map object to which treasureMap refers. Finally, the last line prints the value that corresponds to the key 2 in the treasureMap. If you run this code, it will print:
Find big X on ground.
If you prefer an immutable map, no import is necessary, as immutable is the default map. An example is shown in Listing 3.8:
val romanNumeral = Map(
1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
println(romanNumeral(4))
Listing 3.8 · Creating, initializing, and using an immutable map.
Given there are no imports, when you say Map in the first line of Listing 3.8, you’ll get the default: a scala.collection.immutable.Map. You pass five key/value tuples to the map’s factory method, which returns an immutable Map containing the passed key/value pairs. If you run the code in Listing 3.8 it will print “IV”.
7The explicit type parameterization, “[Int, String]”, is required in Listing 3.7 because without any values passed to the factory method, the compiler is unable to infer the map’s type parameters. By contrast, the compiler can infer the type parameters from the values passed to the map factory shown in Listing 3.8, thus no explicit type parameters are needed.
8The Scala mechanism that allows you to invoke -> on any object, implicit conversion, will be covered in Chapter 21.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 11 |
Chapter 3 · Next Steps in Scala |
96 |
Step 11. Learn to recognize the functional style
As mentioned in Chapter 1, Scala allows you to program in an imperative style, but encourages you to adopt a more functional style. If you are coming to Scala from an imperative background—for example, if you are a Java programmer—one of the main challenges you may face when learning Scala is figuring out how to program in the functional style. We realize this style might be unfamiliar at first, and in this book we try hard to guide you through the transition. It will require some work on your part, and we encourage you to make the effort. If you come from an imperative background, we believe that learning to program in a functional style will not only make you a better Scala programmer, it will expand your horizons and make you a better programmer in general.
The first step is to recognize the difference between the two styles in code. One telltale sign is that if code contains any vars, it is probably in an imperative style. If the code contains no vars at all—i.e., it contains only vals—it is probably in a functional style. One way to move towards a functional style, therefore, is to try to program without vars.
If you’re coming from an imperative background, such as Java, C++, or C#, you may think of var as a regular variable and val as a special kind of variable. On the other hand, if you’re coming from a functional background, such as Haskell, OCaml, or Erlang, you might think of val as a regular variable and var as akin to blasphemy. The Scala perspective, however, is that val and var are just two different tools in your toolbox, both useful, neither inherently evil. Scala encourages you to lean towards vals, but ultimately reach for the best tool given the job at hand. Even if you agree with this balanced philosophy, however, you may still find it challenging at first to figure out how to get rid of vars in your code.
Consider the following while loop example, adapted from Chapter 2, which uses a var and is therefore in the imperative style:
def printArgs(args: Array[String]): Unit = { var i = 0
while (i < args.length) { println(args(i))
i += 1
}
}
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Step 11 |
Chapter 3 · Next Steps in Scala |
97 |
You can transform this bit of code into a more functional style by getting rid of the var, for example, like this:
def printArgs(args: Array[String]): Unit = { for (arg <- args)
println(arg)
}
or this:
def printArgs(args: Array[String]): Unit = { args.foreach(println)
}
This example illustrates one benefit of programming with fewer vars. The refactored (more functional) code is clearer, more concise, and less error-prone than the original (more imperative) code. The reason Scala encourages a functional style, in fact, is that the functional style can help you write more understandable, less error-prone code.
You can go even further, though. The refactored printArgs method is not purely functional, because it has side effects—in this case, its side effect is printing to the standard output stream. The telltale sign of a function with side effects is that its result type is Unit. If a function isn’t returning any interesting value, which is what a result type of Unit means, the only way that function can make a difference in the world is through some kind of side effect. A more functional approach would be to define a method that formats the passed args for printing, but just returns the formatted string, as shown in Listing 3.9:
def formatArgs(args: Array[String]) = args.mkString("\n")
Listing 3.9 · A function without side effects or vars.
Now you’re really functional: no side effects or vars in sight. The mkString method, which you can call on any iterable collection (including arrays, lists, sets, and maps), returns a string consisting of the result of calling toString on each element, separated by the passed string. Thus if args contains three elements "zero", "one", and "two", formatArgs will return "zero\none\ntwo". Of course, this function doesn’t actually print
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index