- •Contents
- •List of Figures
- •List of Tables
- •List of Listings
- •Foreword
- •Foreword to the First Edition
- •Acknowledgments
- •Introduction
- •A language that grows on you
- •A Scalable Language
- •What makes Scala scalable?
- •Why Scala?
- •Conclusion
- •First Steps in Scala
- •Conclusion
- •Next Steps in Scala
- •Conclusion
- •Classes and Objects
- •Semicolon inference
- •Singleton objects
- •A Scala application
- •Conclusion
- •Basic Types and Operations
- •Some basic types
- •Literals
- •Operators are methods
- •Arithmetic operations
- •Relational and logical operations
- •Bitwise operations
- •Object equality
- •Operator precedence and associativity
- •Rich wrappers
- •Conclusion
- •Functional Objects
- •Checking preconditions
- •Self references
- •Auxiliary constructors
- •Method overloading
- •Implicit conversions
- •A word of caution
- •Conclusion
- •Built-in Control Structures
- •If expressions
- •While loops
- •For expressions
- •Match expressions
- •Variable scope
- •Conclusion
- •Functions and Closures
- •Methods
- •Local functions
- •Short forms of function literals
- •Placeholder syntax
- •Partially applied functions
- •Closures
- •Special function call forms
- •Tail recursion
- •Conclusion
- •Control Abstraction
- •Reducing code duplication
- •Simplifying client code
- •Currying
- •Writing new control structures
- •Conclusion
- •Composition and Inheritance
- •A two-dimensional layout library
- •Abstract classes
- •Extending classes
- •Invoking superclass constructors
- •Polymorphism and dynamic binding
- •Using composition and inheritance
- •Heighten and widen
- •Putting it all together
- •Conclusion
- •How primitives are implemented
- •Bottom types
- •Conclusion
- •Traits
- •How traits work
- •Thin versus rich interfaces
- •Example: Rectangular objects
- •The Ordered trait
- •Why not multiple inheritance?
- •To trait, or not to trait?
- •Conclusion
- •Packages and Imports
- •Putting code in packages
- •Concise access to related code
- •Imports
- •Implicit imports
- •Package objects
- •Conclusion
- •Assertions and Unit Testing
- •Assertions
- •Unit testing in Scala
- •Informative failure reports
- •Using JUnit and TestNG
- •Conclusion
- •Property-based testing
- •Organizing and running tests
- •Case Classes and Pattern Matching
- •A simple example
- •Kinds of patterns
- •Pattern guards
- •Pattern overlaps
- •Sealed classes
- •The Option type
- •Patterns everywhere
- •A larger example
- •Conclusion
- •Working with Lists
- •List literals
- •The List type
- •Constructing lists
- •Basic operations on lists
- •List patterns
- •First-order methods on class List
- •Methods of the List object
- •Processing multiple lists together
- •Conclusion
- •Collections
- •Sequences
- •Sets and maps
- •Selecting mutable versus immutable collections
- •Initializing collections
- •Tuples
- •Conclusion
- •Stateful Objects
- •What makes an object stateful?
- •Reassignable variables and properties
- •Case study: Discrete event simulation
- •A language for digital circuits
- •The Simulation API
- •Circuit Simulation
- •Conclusion
- •Type Parameterization
- •Functional queues
- •Information hiding
- •Variance annotations
- •Checking variance annotations
- •Lower bounds
- •Contravariance
- •Object private data
- •Upper bounds
- •Conclusion
- •Abstract Members
- •A quick tour of abstract members
- •Type members
- •Abstract vals
- •Abstract vars
- •Initializing abstract vals
- •Abstract types
- •Path-dependent types
- •Structural subtyping
- •Enumerations
- •Case study: Currencies
- •Conclusion
- •Implicit Conversions and Parameters
- •Implicit conversions
- •Rules for implicits
- •Implicit conversion to an expected type
- •Converting the receiver
- •Implicit parameters
- •View bounds
- •When multiple conversions apply
- •Debugging implicits
- •Conclusion
- •Implementing Lists
- •The List class in principle
- •The ListBuffer class
- •The List class in practice
- •Functional on the outside
- •Conclusion
- •For Expressions Revisited
- •For expressions
- •The n-queens problem
- •Querying with for expressions
- •Translation of for expressions
- •Going the other way
- •Conclusion
- •The Scala Collections API
- •Mutable and immutable collections
- •Collections consistency
- •Trait Traversable
- •Trait Iterable
- •Sets
- •Maps
- •Synchronized sets and maps
- •Concrete immutable collection classes
- •Concrete mutable collection classes
- •Arrays
- •Strings
- •Performance characteristics
- •Equality
- •Views
- •Iterators
- •Creating collections from scratch
- •Conversions between Java and Scala collections
- •Migrating from Scala 2.7
- •Conclusion
- •The Architecture of Scala Collections
- •Builders
- •Factoring out common operations
- •Integrating new collections
- •Conclusion
- •Extractors
- •An example: extracting email addresses
- •Extractors
- •Patterns with zero or one variables
- •Variable argument extractors
- •Extractors and sequence patterns
- •Extractors versus case classes
- •Regular expressions
- •Conclusion
- •Annotations
- •Why have annotations?
- •Syntax of annotations
- •Standard annotations
- •Conclusion
- •Working with XML
- •Semi-structured data
- •XML overview
- •XML literals
- •Serialization
- •Taking XML apart
- •Deserialization
- •Loading and saving
- •Pattern matching on XML
- •Conclusion
- •Modular Programming Using Objects
- •The problem
- •A recipe application
- •Abstraction
- •Splitting modules into traits
- •Runtime linking
- •Tracking module instances
- •Conclusion
- •Object Equality
- •Equality in Scala
- •Writing an equality method
- •Recipes for equals and hashCode
- •Conclusion
- •Combining Scala and Java
- •Using Scala from Java
- •Annotations
- •Existential types
- •Using synchronized
- •Compiling Scala and Java together
- •Conclusion
- •Actors and Concurrency
- •Trouble in paradise
- •Actors and message passing
- •Treating native threads as actors
- •Better performance through thread reuse
- •Good actors style
- •A longer example: Parallel discrete event simulation
- •Conclusion
- •Combinator Parsing
- •Example: Arithmetic expressions
- •Running your parser
- •Basic regular expression parsers
- •Another example: JSON
- •Parser output
- •Implementing combinator parsers
- •String literals and regular expressions
- •Lexing and parsing
- •Error reporting
- •Backtracking versus LL(1)
- •Conclusion
- •GUI Programming
- •Panels and layouts
- •Handling events
- •Example: Celsius/Fahrenheit converter
- •Conclusion
- •The SCells Spreadsheet
- •The visual framework
- •Disconnecting data entry and display
- •Formulas
- •Parsing formulas
- •Evaluation
- •Operation libraries
- •Change propagation
- •Conclusion
- •Scala Scripts on Unix and Windows
- •Glossary
- •Bibliography
- •About the Authors
- •Index
Chapter 9
Control Abstraction
In Chapter 7, we pointed out that Scala doesn’t have many built-in control abstractions, because it gives you the ability to create your own. In the previous chapter, you learned about function values. In this chapter, we’ll show you how to apply function values to create new control abstractions. Along the way, you’ll also learn about currying and by-name parameters.
9.1Reducing code duplication
All functions are separated into common parts, which are the same in every invocation of the function, and non-common parts, which may vary from one function invocation to the next. The common parts are in the body of the function, while the non-common parts must be supplied via arguments. When you use a function value as an argument, the non-common part of the algorithm is itself some other algorithm! At each invocation of such a function, you can pass in a different function value as an argument, and the invoked function will, at times of its choosing, invoke the passed function value. These higher-order functions—functions that take functions as parameters—give you extra opportunities to condense and simplify code.
One benefit of higher-order functions is they enable you to create control abstractions that allow you to reduce code duplication. For example, suppose you are writing a file browser, and you want to provide an API that allows users to search for files matching some criterion. First, you add a facility to search for files whose names end in a particular string. This would enable your users to find, for example, all files with a “.scala” extension. You could provide such an API by defining a public filesEnding method inside
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 9.1 |
Chapter 9 · Control Abstraction |
208 |
a singleton object like this:
object FileMatcher {
private def filesHere = (new java.io.File(".")).listFiles
def filesEnding(query: String) =
for (file <- filesHere; if file.getName.endsWith(query)) yield file
}
The filesEnding method obtains the list of all files in the current directory using the private helper method filesHere, then filters them based on whether each file name ends with the user-specified query. Given filesHere is private, the filesEnding method is the only accessible method defined in FileMatcher, the API you provide to your users.
So far so good, and there is no repeated code yet. Later on, though, you decide to let people search based on any part of the file name. This is good for when your users cannot remember if they named a file phb-important.doc, stupid-phb-report.doc, may2003salesdoc.phb, or something entirely different, but they think that “phb” appears in the name somewhere. You go back to work and add this function to your FileMatcher API:
def filesContaining(query: String) =
for (file <- filesHere; if file.getName.contains(query)) yield file
This function works just like filesEnding. It searches filesHere, checks the name, and returns the file if the name matches. The only difference is that this function uses contains instead of endsWith.
The months go by, and the program becomes more successful. Eventually, you give in to the requests of a few power users who want to search based on regular expressions. These sloppy guys have immense directories with thousands of files, and they would like to do things like find all “pdf” files that have “oopsla” in the title somewhere. To support them, you write this function:
def filesRegex(query: String) =
for (file <- filesHere; if file.getName.matches(query)) yield file
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 9.1 |
Chapter 9 · Control Abstraction |
209 |
Experienced programmers will notice all of this repetition and wonder if it can be factored into a common helper function. Doing it the obvious way does not work, however. You would like to be able to do the following:
def filesMatching(query: String, method) =
for (file <- filesHere; if file.getName.method(query)) yield file
This approach would work in some dynamic languages, but Scala does not allow pasting together code at runtime like this. So what do you do?
Function values provide an answer. While you cannot pass around a method name as a value, you can get the same effect by passing around a function value that calls the method for you. In this case, you could add a matcher parameter to the method whose sole purpose is to check a file name against a query:
def filesMatching(query: String,
matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query)) yield file
}
In this version of the method, the if clause now uses matcher to check the file name against the query. Precisely what this check does depends on what is specified as the matcher. Take a look, now, at the type of matcher itself. It is a function, and thus has a => in the type. This function takes two string arguments—the file name and the query—and returns a boolean, so the type of this function is (String, String) => Boolean.
Given this new filesMatching helper method, you can simplify the three searching methods by having them call the helper method, passing in an appropriate function:
def filesEnding(query: String) = filesMatching(query, _.endsWith(_))
def filesContaining(query: String) = filesMatching(query, _.contains(_))
def filesRegex(query: String) = filesMatching(query, _.matches(_))
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 9.1 |
Chapter 9 · Control Abstraction |
210 |
The function literals shown in this example use the placeholder syntax, introduced in the previous chapter, which may not as yet feel very natural to you. Thus, here’s a clarification of how placeholders are used in this example. The function literal _.endsWith(_), used in the filesEnding method, means the same thing as:
(fileName: String, query: String) => fileName.endsWith(query)
Because filesMatching takes a function that requires two String arguments, however, you need not specify the types of the arguments. Thus you could also write (fileName, query) => fileName.endsWith(query). Since the parameters are each used only once in the body of the function, and since the first parameter, fileName, is used first in the body, and the second parameter, query, is used second, you can use the placeholder syntax: _.endsWith(_). The first underscore is a placeholder for the first parameter, the file name, and the second underscore a placeholder for the second parameter, the query string.
This code is already simplified, but it can actually be even shorter. Notice that the query gets passed to filesMatching, but filesMatching does nothing with the query except to pass it back to the passed matcher function. This passing back and forth is unnecessary, because the caller already knew the query to begin with! You might as well simply remove the query parameter from filesMatching and matcher, thus simplifying the code as shown in Listing 9.1.
This example demonstrates the way in which first-class functions can help you eliminate code duplication where it would be very difficult to do so without them. In Java, for example, you could create an interface containing a method that takes one String and returns a Boolean, then create and pass anonymous inner class instances that implement this interface to filesMatching. Although this approach would remove the code duplication you are trying to eliminate, it would at the same time add as much or more new code. Thus the benefit is not worth the cost, and you may as well live with the duplication.
Moreover, this example demonstrates how closures can help you reduce code duplication. The function literals used in the previous example, such as _.endsWith(_) and _.contains(_), are instantiated at runtime into function values that are not closures, because they don’t capture any free variables. Both variables used in the expression, _.endsWith(_), for example, are represented by underscores, which means they are taken from arguments
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
