ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3428
Скачиваний: 0
Section 1.2 |
Chapter 1 · A Scalable Language |
55 |
that it is feasible—you can design and implement abstractions in Scala that address radically new application domains, yet still feel like native language support.
1.2What makes Scala scalable?
Scalability is influenced by many factors, ranging from syntax details to component abstraction constructs. If we were forced to name just one aspect of Scala that helps scalability, though, we’d pick its combination of object-oriented and functional programming (well, we cheated, that’s really two aspects, but they are intertwined).
Scala goes further than all other well-known languages in fusing objectoriented and functional programming into a uniform language design. For instance, where other languages might have objects and functions as two different concepts, in Scala a function value is an object. Function types are classes that can be inherited by subclasses. This might seem nothing more than an academic nicety, but it has deep consequences for scalability. In fact the actor concept shown previously could not have been implemented without this unification of functions and objects. This section gives an overview of Scala’s way of blending object-oriented and functional concepts.
Scala is object-oriented
Object-oriented programming has been immensely successful. Starting from Simula in the mid-60’s and Smalltalk in the 70’s, it is now available in more languages than not. In some domains objects have taken over completely. While there is not a precise definition of what object-oriented means, there is clearly something about objects that appeals to programmers.
In principle, the motivation for object-oriented programming is very simple: all but the most trivial programs need some sort of structure. The most straightforward way to do this is to put data and operations into some form of containers. The great idea of object-oriented programming is to make these containers fully general, so that they can contain operations as well as data, and that they are themselves values that can be stored in other containers, or passed as parameters to operations. Such containers are called objects. Alan Kay, the inventor of Smalltalk, remarked that in this way the simplest object has the same construction principle as a full computer: it combines data with
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.2 |
Chapter 1 · A Scalable Language |
56 |
operations under a formalized interface.7 So objects have a lot to do with language scalability: the same techniques apply to the construction of small as well as large programs.
Even though object-oriented programming has been mainstream for a long time, there are relatively few languages that have followed Smalltalk in pushing this construction principle to its logical conclusion. For instance, many languages admit values that are not objects, such as the primitive values in Java. Or they allow static fields and methods that are not members of any object. These deviations from the pure idea of object-oriented programming look quite harmless at first, but they have an annoying tendency to complicate things and limit scalability.
By contrast, Scala is an object-oriented language in pure form: every value is an object and every operation is a method call. For example, when you say 1 + 2 in Scala, you are actually invoking a method named + defined in class Int. You can define methods with operator-like names that clients of your API can then use in operator notation. This is how the designer of Scala’s actors API enabled you to use expressions such as requester ! sum shown in the previous example: ‘!’ is a method of the Actor class.
Scala is more advanced than most other languages when it comes to composing objects. An example is Scala’s traits. Traits are like interfaces in Java, but they can also have method implementations and even fields. Objects are constructed by mixin composition, which takes the members of a class and adds the members of a number of traits to them. In this way, different aspects of classes can be encapsulated in different traits. This looks a bit like multiple inheritance, but differs when it comes to the details. Unlike a class, a trait can add some new functionality to an unspecified superclass. This makes traits more “pluggable” than classes. In particular, it avoids the classical “diamond inheritance” problems of multiple inheritance, which arise when the same class is inherited via several different paths.
Scala is functional
In addition to being a pure object-oriented language, Scala is also a fullblown functional language. The ideas of functional programming are older than (electronic) computers. Their foundation was laid in Alonzo Church’s lambda calculus, which he developed in the 1930s. The first functional programming language was Lisp, which dates from the late 50s. Other popular
7Kay, “The Early History of Smalltalk.” [Kay96]
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.2 |
Chapter 1 · A Scalable Language |
57 |
functional languages are Scheme, SML, Erlang, Haskell, OCaml, and F#. For a long time, functional programming has been a bit on the sidelines, popular in academia, but not that widely used in industry. However, recent years have seen an increased interest in functional programming languages and techniques.
Functional programming is guided by two main ideas. The first idea is that functions are first-class values. In a functional language, a function is a value of the same status as, say, an integer or a string. You can pass functions as arguments to other functions, return them as results from functions, or store them in variables. You can also define a function inside another function, just as you can define an integer value inside a function. And you can define functions without giving them a name, sprinkling your code with function literals as easily as you might write integer literals like 42.
Functions that are first-class values provide a convenient means for abstracting over operations and creating new control structures. This generalization of functions provides great expressiveness, which often leads to very legible and concise programs. It also plays an important role for scalability. As an example, the receive construct shown previously in the actor example is an invocation of a method that takes a function as argument. The code inside the receive construct is a function that is passed unexecuted into the receive method.
In most traditional languages, by contrast, functions are not values. Languages that do have function values often relegate them to second-class status. For example, the function pointers of C and C++ do not have the same status as non-functional values in those languages: function pointers can only refer to global functions, they do not allow you to define first-class nested functions that refer to some values in their environment. Nor do they allow you to define unnamed function literals.
The second main idea of functional programming is that the operations of a program should map input values to output values rather than change data in place. To see the difference, consider the implementation of strings in Ruby and in Java. In Ruby, a string is an array of characters. Characters in a string can be changed individually. For instance you can change a semicolon character in a string to a period inside the same string object. In Java and Scala, on the other hand, a string is a sequence of characters in the mathematical sense. Replacing a character in a string using an expression like s.replace(';', '.') yields a new string object, which is different from s. Another way of expressing this is that strings are immutable in Java
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
58 |
whereas they are mutable in Ruby. So looking at just strings, Java is a functional language, whereas Ruby is not. Immutable data structures are one of the cornerstones of functional programming. The Scala libraries define many more immutable data types on top of those found in the Java APIs. For instance, Scala has immutable lists, tuples, maps, and sets.
Another way of stating this second idea of functional programming is that methods should not have any side effects. They should communicate with their environment only by taking arguments and returning results. For instance, the replace method in Java’s String class fits this description. It takes a string and two characters and yields a new string where all occurrences of one character are replaced by the other. There is no other effect of calling replace. Methods like replace are called referentially transparent, which means that for any given input the method call could be replaced by its result without affecting the program’s semantics.
Functional languages encourage immutable data structures and referentially transparent methods. Some functional languages even require them. Scala gives you a choice. When you want to, you can write in an imperative style, which is what programming with mutable data and side effects is called. But Scala generally makes it easy to avoid imperative constructs when you want, because good functional alternatives exist.
1.3Why Scala?
Is Scala for you? You will have to see and decide for yourself. We have found that there are actually many reasons besides scalability to like programming in Scala. Four of the most important aspects will be discussed in this section: compatibility, brevity, high-level abstractions, and advanced static typing.
Scala is compatible
Scala doesn’t require you to leap backwards off the Java platform to step forward from the Java language. It allows you to add value to existing code—to build on what you already have—because it was designed for seamless interoperability with Java.8 Scala programs compile to JVM bytecodes. Their run-time performance is usually on par with Java programs. Scala code can
8There is also a Scala variant that runs on the .NET platform, but the JVM variant currently has better support.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
59 |
call Java methods, access Java fields, inherit from Java classes, and implement Java interfaces. None of this requires special syntax, explicit interface descriptions, or glue code. In fact, almost all Scala code makes heavy use of Java libraries, often without programmers being aware of this fact.
Another aspect of full interoperability is that Scala heavily re-uses Java types. Scala’s Ints are represented as Java primitive integers of type int, Floats are represented as floats, Booleans as booleans, and so on. Scala arrays are mapped to Java arrays. Scala also re-uses many of the standard Java library types. For instance, the type of a string literal "abc" in Scala is java.lang.String, and a thrown exception must be a subclass of java.lang.Throwable.
Scala not only re-uses Java’s types, but also “dresses them up” to make them nicer. For instance, Scala’s strings support methods like toInt or toFloat, which convert the string to an integer or floating-point number. So you can write str.toInt instead of Integer.parseInt(str). How can this be achieved without breaking interoperability? Java’s String class certainly has no toInt method! In fact, Scala has a very general solution to solve this tension between advanced library design and interoperability. Scala lets you define implicit conversions, which are always applied when types would not normally match up, or when non-existing members are selected. In the case above, when looking for a toInt method on a string, the Scala compiler will find no such member of class String, but it will find an implicit conversion that converts a Java String to an instance of the Scala class StringOps, which does define such a member. The conversion will then be applied implicitly before performing the toInt operation.
Scala code can also be invoked from Java code. This is sometimes a bit more subtle, because Scala is a richer language than Java, so some of Scala’s more advanced features need to be encoded before they can be mapped to Java. Chapter 31 explains the details.
Scala is concise
Scala programs tend to be short. Scala programmers have reported reductions in number of lines of up to a factor of ten compared to Java. These might be extreme cases. A more conservative estimate would be that a typical Scala program should have about half the number of lines of the same program written in Java. Fewer lines of code mean not only less typing, but also less effort at reading and understanding programs and fewer possibili-
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
60 |
ties of defects. There are several factors that contribute to this reduction in lines of code.
First, Scala’s syntax avoids some of the boilerplate that burdens Java programs. For instance, semicolons are optional in Scala and are usually left out. There are also several other areas where Scala’s syntax is less noisy. As an example, compare how you write classes and constructors in Java and Scala. In Java, a class with a constructor often looks like this:
// this is Java class MyClass {
private int index; private String name;
public MyClass(int index, String name) { this.index = index;
this.name = name;
}
}
In Scala, you would likely write this instead:
class MyClass(index: Int, name: String)
Given this code, the Scala compiler will produce a class that has two private instance variables, an Int named index and a String named name, and a constructor that takes initial values for those variables as parameters. The code of this constructor will initialize the two instance variables with the values passed as parameters. In short, you get essentially the same functionality as the more verbose Java version.9 The Scala class is quicker to write, easier to read, and most importantly, less error prone than the Java class.
Scala’s type inference is another factor that contributes to its conciseness. Repetitive type information can be left out, so programs become less cluttered and more readable.
But probably the most important key to compact code is code you don’t have to write because it is done in a library for you. Scala gives you many tools to define powerful libraries that let you capture and factor out common behavior. For instance, different aspects of library classes can be separated
9The only real difference is that the instance variables produced in the Scala case will be final. You’ll learn how to make them non-final in Section 10.6.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
61 |
out into traits, which can then be mixed together in flexible ways. Or, library methods can be parameterized with operations, which lets you define constructs that are, in effect, your own control structures. Together, these constructs allow the definition of libraries that are both high-level and flexible to use.
Scala is high-level
Programmers are constantly grappling with complexity. To program productively, you must understand the code on which you are working. Overly complex code has been the downfall of many a software project. Unfortunately, important software usually has complex requirements. Such complexity can’t be avoided; it must instead be managed.
Scala helps you manage complexity by letting you raise the level of abstraction in the interfaces you design and use. As an example, imagine you have a String variable name, and you want to find out whether or not that String contains an upper case character. In Java, you might write this:
// this is Java
boolean nameHasUpperCase = false;
for (int i = 0; i < name.length(); ++i) {
if (Character.isUpperCase(name.charAt(i))) { nameHasUpperCase = true;
break;
}
}
Whereas in Scala, you could write this:
val nameHasUpperCase = name.exists(_.isUpper)
The Java code treats strings as low-level entities that are stepped through character by character in a loop. The Scala code treats the same strings as higher-level sequences of characters that can be queried with predicates. Clearly the Scala code is much shorter and—for trained eyes—easier to understand than the Java code. So the Scala code weighs less heavily on the total complexity budget. It also gives you less opportunity to make mistakes.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
62 |
The predicate _.isUpper is an example of a function literal in Scala.10 It describes a function that takes a character argument (represented by the underscore character), and tests whether it is an upper case letter.11
In principle, such control abstractions are possible in Java as well. You’d need to define an interface that contains a method with the abstracted functionality. For instance, if you wanted to support querying over strings, you might invent an interface, named CharacterProperty, which has just one method, hasProperty:
// this is Java
interface CharacterProperty { boolean hasProperty(char ch);
}
With that interface you could formulate a method exists in Java: It takes a string and CharacterProperty and returns true if there’s a character in the string that satisfies the property. You could then invoke exists as follows:
// this is Java
exists(name, new CharacterProperty() { public boolean hasProperty(char ch) {
return Character.isUpperCase(ch);
}
});
However, all this feels rather heavy. So heavy, in fact, that most Java programmers would not bother. They would just write out the loops and live with the increased complexity in their code. On the other hand, function literals in Scala are really lightweight, so they are used frequently. As you get to know Scala better you’ll find more and more opportunities to define and use your own control abstractions. You’ll find that this helps avoid code duplication and thus keeps your programs shorter and clearer.
Scala is statically typed
A static type system classifies variables and expressions according to the kinds of values they hold and compute. Scala stands out as a language with
10A function literal can be called a predicate if its result type is Boolean.
11This use of the underscore as a placeholder for arguments is described in Section 8.5.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
63 |
a very advanced static type system. Starting from a system of nested class types much like Java’s, it allows you to parameterize types with generics, to combine types using intersections, and to hide details of types using abstract types.12 These give a strong foundation for building and composing your own types, so that you can design interfaces that are at the same time safe and flexible to use.
If you like dynamic languages such as Perl, Python, Ruby, or Groovy, you might find it a bit strange that Scala’s static type system is listed as one of its strong points. After all, the absence of a static type system has been cited by some as a major advantage of dynamic languages. The most common arguments against static types are that they make programs too verbose, prevent programmers from expressing themselves as they wish, and make impossible certain patterns of dynamic modifications of software systems. However, often these arguments do not go against the idea of static types in general, but against specific type systems, which are perceived to be too verbose or too inflexible. For instance, Alan Kay, the inventor of the Smalltalk language, once remarked: “I’m not against types, but I don’t know of any type systems that aren’t a complete pain, so I still like dynamic typing.”13
We hope to convince you in this book that Scala’s type system is far from being a “complete pain.” In fact, it addresses nicely two of the usual concerns about static typing: verbosity is avoided through type inference and flexibility is gained through pattern matching and several new ways to write and compose types. With these impediments out of the way, the classical benefits of static type systems can be better appreciated. Among the most important of these benefits are verifiable properties of program abstractions, safe refactorings, and better documentation.
Verifiable properties. Static type systems can prove the absence of certain run-time errors. For instance, they can prove properties like: booleans are never added to integers; private variables are not accessed from outside their class; functions are applied to the right number of arguments; only strings are ever added to a set of strings.
Other kinds of errors are not detected by today’s static type systems. For instance, they will usually not detect non-terminating functions, array
12Generics are discussed in Chapter 19, intersections in Chapter 12, and abstract types in Chapter 20.
13Kay, in an email on the meaning of object-oriented programming. [Kay03]
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 1.3 |
Chapter 1 · A Scalable Language |
64 |
bounds violations, or divisions by zero. They will also not detect that your program does not conform to its specification (assuming there is a spec, that is!). Static type systems have therefore been dismissed by some as not being very useful. The argument goes that since such type systems can only detect simple errors, whereas unit tests provide more extensive coverage, why bother with static types at all? We believe that these arguments miss the point. Although a static type system certainly cannot replace unit testing, it can reduce the number of unit tests needed by taking care of some properties that would otherwise need to be tested. Likewise, unit testing can not replace static typing. After all, as Edsger Dijkstra said, testing can only prove the presence of errors, never their absence.14 So the guarantees that static typing gives may be simple, but they are real guarantees of a form no amount of testing can deliver.
Safe refactorings. A static type system provides a safety net that lets you make changes to a codebase with a high degree of confidence. Consider for instance a refactoring that adds an additional parameter to a method. In a statically typed language you can do the change, re-compile your system and simply fix all lines that cause a type error. Once you have finished with this, you are sure to have found all places that need to be changed. The same holds for many other simple refactorings like changing a method name, or moving methods from one class to another. In all cases a static type check will provide enough assurance that the new system works just like the old.
Documentation. Static types are program documentation that is checked by the compiler for correctness. Unlike a normal comment, a type annotation can never be out of date (at least not if the source file that contains it has recently passed a compiler). Furthermore, compilers and integrated development environments can make use of type annotations to provide better context help. For instance, an integrated development environment can display all the members available for a selection by determining the static type of the expression on which the selection is made and looking up all members of that type.
Even though static types are generally useful for program documentation, they can sometimes be annoying when they clutter the program. Typically,
14Dijkstra, “Notes on Structured Programming,” [Dij70]
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index