Scala Tutorial (3)

2008-12-27

Now that we have covered the somewhat “dry” foundations of the Scala language, let’s move on to the good stuff. That is to say, let’s move on to the functional aspects of Scala. Some of these may appear foreign to you if your main language is object-oriented or procedural. They definitely appeared foreign to me at first. It cannot be stressed enough that functions are primary language constructs in Scala. I mentioned function literals. Here is an example:

1
(x: Int) => x - 1

A function literal is simply a function without a name. You can assign this expression to an ordinary variable and then use it later to execute the function:

1
2
var decrease = (x: Int) => x - 1
decrease(11)

You can probably guess what the value of decrease(11) is. Up to this point, this isn’t very exiting. However, because decrease is a var and not a val, you can reassign it a different function literal later on depending on some condition. Or you could pass it as an argument to another function. You might begin to see the possibilities behind the first class function concept. Instead of the trivial example above you can use this capability with more complex methods. For example, you could use it for callback methods, or for wrapping methods in an API and facilitating common OOP patterns, such as the facade and adapter patterns. All of this can be done, unlike in Java, with a minimum of syntactical fuss.

But it gets even better. Functions in Scala are not limited to use simple parameters as in the above example. For example, you could write something like this:

1
var decrease = (x: Int) => x – something

Obviously, the variable something, which is subtracted from x is not defined in this expression. The assignment is still syntactically valid. This means the variable decrease is properly defined, but it cannot be applied. In other words, to apply a value or to execute the function meaningfully, it must be executed in a context where something is defined. The above expression is an “open term” and something is called a “free variable”. An open term is the principal ingredient of a closure. Simply put, a closure closes an open term by providing the necessary context. This is an example of a closure:

1
2
something = 10
decrease(100)

The first line defines the free variable something. The second line therefore produces a meaningful value. That’s the closure. In terms of computer science, a closure is a function that is evaluated and whose result depend on an outer context. It’s like an inner class in Java that references variables of its outer class. However, the Scala construct is more powerful and much easier to write. It can be useful in a number of situations where the place where behaviour is defined is removed from the place where the behaviour is applied.

While closures are supported by languages that one wouldn’t consider functional programming languages, higher-order functions are a hallmark of the functional paradigm. The concept is simple enough. A higher-order function is a function that either takes one or more function parameters or returns a function, or both. The prime example would be the map function. The map function takes an list as input and produces an output list using a specific algorithm. The algorithm is of course represented by another function that is passed as a parameter to the map function. In Java, this would typically require a the use of a for loop or an iterator. In Scala, the mapping can be expressed succinctly with a single line. Here are some examples:

1
2
3
4
5
6
7
8
9
10
scala> List("alpha", "beta", "crash").map(
p => p.substring(0,1))
res0: List[java.lang.String] = List(a, b, c)

scala> List(1, 2, 3) map (_ * 2)
res1: List[Int] = List(2, 4, 6)

scala> List("Tango", "Mango", "Bobo", "Gogo")
map (_.endsWith("go"))
res2: List[Boolean] = List(true, true, false, true)

These examples are quite trivial, but they illustrate the basic idea of higher-order functions. The first line maps a list of strings to another list of strings. The result elements retain only the first characters. The second line is produced by the interactive Scala Interpreter; it displays the result of the expression. A function literal is used as the parameter of the map function in line 1. Line 5 and 9 make use of the underscore notation which is a placeholder for the bound variable, which is the list element parameter in this case. Thus (_2) is equivalent to (i => i 2).

Higher-order functions are not just a succinct form of control abstraction. They are also very useful for reducing code duplication and redundancy. The typical use case is abstracting behaviour. There are a number of design patterns for abstracting behaviour which are popular in languages like C++ or Java, such as the strategy pattern. Consider the following Java example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.io.*;
import java.util.*;

interface FileNameMatcher {
boolean matchFileName(String fileName, String query);
}

class FileNameMatcherEnding implements FileNameMatcher {
public boolean matchFileName(String fileName,
String query) {
return fileName.endsWith(query);
}
}

class FileNameMatcherContaining implements FileNameMatcher {
public boolean matchFileName(String fileName,
String query) {
return fileName.contains(query);
}
}

class FileNameMatcherRegex implements FileNameMatcher {
public boolean matchFileName(String fileName,
String query) {
return fileName.matches(query);
}
}

public class FileMatcher {

public static String[] filesMatching(FileNameMatcher
fileNameMatcher, String query) {
File[] files = new File(".").listFiles();
List<string> matchResult = new ArrayList<string>();
for (File thisFile : files) {
if(fileNameMatcher.matchFileName(
thisFile.toString(), query))
matchResult.add(thisFile.toString());
}
return matchResult.toArray(new String[] {});
}

public static void main(String[] args) {
String query = args[0];
String[] matchingFiles;
matchingFiles = filesMatching(
new FileNameMatcherEnding(), query);
matchingFiles = filesMatching(
new FileNameMatcherContaining(), query);
matchingFiles = filesMatching(
new FileNameMatcherRegex(), query);
}

}

This is a classic example of the strategy pattern. The core piece of this code is the one-method interface named FileNameMatcher. This interface abstracts different ways to match a file name. The three implementing methods match a file name either by looking whether a file name ends with a given string, whether it contains a given string, or whether it matches a regular expression. The filesMatching() method of the FileMatcher class takes a strategy implementation and applies it to a list that contains all files in the current directory. Finally, the main() function calls the FileMatcher with the three different strategy objects that were defined earlier. All together that’s 55 lines of Java code. Compare this to the Scala equivalent which appears in the book Programming in Scala by Odersky, Spoon and Venners (page 199):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
object FileMatcher {

private val filesHere=(new java.io.File(".")).listFiles

private def filesMatching(matcher: String => Boolean)=
for (file <- filesHere; if matcher(file.getName))
yield file

def filesEnding(query: String)=
filesMatching(_.endsWith(query))
def filesContaining(query: String)=
filesMatching(_.contains(query))
def filesRegex(query: String)=
filesMatching(_.matches(query))
}

object Main {
def main(args: Array[String]) {
val query = args(0);
var matchingFiles = FileMatcher.filesEnding(query)
matchingFiles = FileMatcher.filesContaining(query)
matchingFiles = FileMatcher.filesRegex(query)
}
}

Brevity is the soul of wit. Obviously, there is no need to create strategy objects in Scala. With higher-order functions, the strategy pattern simply evaporates. This is one of the areas where Scala’s powerful language constructs eliminate the need for patterns. Critical voices have said that design patterns are crutches for weaknesses in certain programming languages. Although I don’t fully agree with this statement, it seems to hold true in case of the strategy pattern. The above Scala example is actually more verbose than necessary. One could make the filesMatching() method public and call it directly with a function parameter from outside the FileMatcher object. At the cost of full encapsulation, this would make the code three lines shorter.

Previous