Chapter 1. Zero to Sixty: Introducing Scala
Why Scala?
Today’s enterprise and Internet applications must balance a number of concerns. They must be implemented quickly and reliably. New features must be added in short, incremental cycles. Beyond simply providing business logic, applications must support secure access, persistence of data, transactional behavior, and other advanced features. Applications must be highly available and scalable, requiring designs that support concurrency and distribution. Applications are networked and provide interfaces for both people and other applications to use.
To meet these challenges, many developers are looking for new languages and tools. Venerable standbys like Java, C#, and C++ are no longer optimal for developing the next generation of applications.
If You Are a Java Programmer…
Java was officially introduced by Sun Microsystems in May of 1995, at the advent of widespread interest in the Internet. Java was immediately hailed as an ideal language for writing browser-based applets, where a secure, portable, and developer-friendly application language was needed. The reigning language of the day, C++, was not suitable for this domain.
Today, Java is more often used for server-side applications. It is one of the most popular languages in use for the development of web and enterprise applications.
However, Java was a child of its time. Now it shows its age. In 1995, Java provided a syntax similar enough to C++ to entice C++ developers, while avoiding many of that language’s deficiencies and “sharp edges.” Java adopted the most useful ideas for the development problems of its era, such as object-oriented programming (OOP), while discarding more troublesome techniques, such as manual memory management. These design choices struck an excellent balance that minimized complexity and maximized developer productivity, while trading-off performance compared to natively compiled code. While Java has evolved since its birth, many people believe it has grown too complex without adequately addressing some newer development challenges.
Developers want languages that are more succinct and flexible to improve their productivity. This is one reason why so-called scripting languages like Ruby and Python have become more popular recently.
The never-ending need to scale is driving architectures toward pervasive concurrency. However, Java’s concurrency model, which is based on synchronized access to shared, mutable state, results in complex and error-prone programs.
While the Java language is showing its age, the Java Virtual Machine (JVM) on which it runs continues to shine. The optimizations performed by today’s JVM are extraordinary, allowing byte code to outperform natively compiled code in many cases. Today, many developers believe that using the JVM with new languages is the path forward. Sun is embracing this trend by employing many of the lead developers of JRuby and Jython, which are JVM ports of Ruby and Python, respectively.
The appeal of Scala for the Java developer is that it gives you a newer, more modern language, while leveraging the JVM’s amazing performance and the wealth of Java libraries that have been developed for over a decade.
If You Are a Ruby, Python, etc. Programmer…
Dynamically typed languages like Ruby, Python, Groovy, JavaScript, and Smalltalk offer very high productivity due to their flexibility, powerful metaprogramming, and elegance.
Despite their productivity advantages, dynamic languages may not be the best choices for all applications, particularly for very large code bases and high-performance applications. There is a longstanding, spirited debate in the programming community about the relative merits of dynamic versus static typing. Many of the points of comparison are somewhat subjective. We won’t go through all the arguments here, but we will offer a few thoughts for consideration.
Optimizing the performance of a dynamic language is more challenging than for a static language. In a static language, optimizers can exploit the type information to make decisions. In a dynamic language, fewer such clues are available for the optimizer, making optimization choices harder. While recent advancements in optimizations for dynamic languages are promising, they lag behind the state of the art for static languages. So, if you require very high performance, static languages are probably a safer choice.
Static languages can also benefit the development process. Integrated development environment (IDE) features like autocompletion (sometimes called code sense) are easier to implement for static languages, again because of the extra type information available. The more explicit type information in static code promotes better “self-documentation,” which can be important for communicating intent among developers, especially as a project grows.
When using a static language, you have to think about appropriate type choices more often, which forces you to weigh design choices more carefully. While this may slow down daily design decisions, thinking through the types in the application can result in a more coherent design over time.
Another small benefit of static languages is the extra checking the compiler performs. We think this advantage is often oversold, as type mismatch errors are a small fraction of the runtime errors you typically see. The compiler can’t find logic errors, which are far more significant. Only a comprehensive, automated test suite can find logic errors. For dynamically typed languages, the tests must cover possible type errors, too. If you are coming from a dynamically typed language, you may find that your test suites are a little smaller as a result, but not that much smaller.
Many developers who find
static languages too verbose often blame static typing for the verbosity
when the real problem is a lack of type inference.
In type inference, the compiler infers the types of values based on the
context. For example, the compiler will recognize that x = 1 +
3
means that x
must be an integer. Type
inference reduces verbosity significantly, making the code feel more
like code written in a dynamic language.
We have worked with both static and dynamic languages, at various times. We find both kinds of languages compelling for different reasons. We believe the modern software developer must master a range of languages and tools. Sometimes, a dynamic language will be the right tool for the job. At other times, a static language like Scala is just what you need.
Introducing Scala
Scala is a language that addresses the major needs of the modern developer. It is a statically typed, mixed-paradigm, JVM language with a succinct, elegant, and flexible syntax, a sophisticated type system, and idioms that promote scalability from small, interpreted scripts to large, sophisticated applications. That’s a mouthful, so let’s look at each of those ideas in more detail:
- Statically typed
As we described in the previous section, a statically typed language binds the type to a variable for the lifetime of that variable. In contrast, dynamically typed languages bind the type to the actual value referenced by a variable, meaning that the type of a variable can change along with the value it references.
Of the set of newer JVM languages, Scala is one of the few that is statically typed, and it is the best known among them.
- Mixed paradigm—object-oriented programming
Scala fully supports object-oriented programming (OOP). Scala improves upon Java’s support for OOP with the addition of traits, a clean way of implementing classes using mixin composition. Scala’s traits work much like Ruby’s modules. If you’re a Java programmer, think of traits as unifying interfaces with their implementations.
In Scala, everything is really an object. Scala does not have primitive types, like Java. Instead, all numeric types are true objects. However, for optimal performance, Scala uses the underlying primitives types of the runtime whenever possible. Also, Scala does not support “static” or class-level members of types, since they are not associated with an actual instance. Instead, Scala supports a singleton object construct to support those cases where exactly one instance of a type is needed.
- Mixed paradigm—functional programming
Scala fully supports functional programming (FP). FP is a programming paradigm that is older than OOP, but it has been sheltered in the ivory towers of academia until recently. Interest in FP is increasing because of the ways it simplifies certain design problems, especially concurrency. “Pure” functional languages don’t allow for any mutable state, thereby avoiding the need for synchronization on shared access to mutable state. Instead, programs written in pure functional languages communicate by passing messages between concurrent, autonomous processes. Scala supports this model with its Actors library, but it allows for both mutable and immutable variables.
Functions are “first-class” citizens in FP, meaning they can be assigned to variables, passed to other functions, etc., just like other values. This feature promotes composition of advanced behavior using primitive operations. Because Scala adheres to the dictum that everything is an object, functions are themselves objects in Scala.
Scala also offers closures, a feature that dynamic languages like Python and Ruby have adopted from the functional programming world, and one sadly absent from recent versions of Java. Closures are functions that reference variables from the scope enclosing the function definition. That is, the variables aren’t passed in as arguments or defined as local variables within the function. A closure “closes around” these references, so the function invocation can safely refer to the variables even when the variables have gone out of scope! Closures are such a powerful abstraction that object systems and fundamental control structures are often implemented using them.
- A JVM and .NET language
While Scala is primarily known as a JVM language, meaning that Scala generates JVM byte code, a .NET version of Scala that generates Common Language Runtime (CLR) byte code is also under development. When we refer to the underlying “runtime,” we will usually discuss the JVM, but most of what we will say applies equally to both runtimes. When we discuss JVM-specific details, they generalize to the .NET version, except where noted.
The Scala compiler uses clever techniques to map Scala extensions to valid byte code idioms. From Scala, you can easily invoke byte code that originated as Java source (for the JVM) or C# source (for .NET). Conversely, you can invoke Scala code from Java, C#, etc. Running on the JVM and CLR allows the Scala developer to leverage available libraries and to interoperate with other languages hosted on those runtimes.
- A succinct, elegant, and flexible syntax
Java syntax can be verbose. Scala uses a number of techniques to minimize unnecessary syntax, making Scala code as succinct as code in most dynamically typed languages. Type inference minimizes the need for explicit type information in many contexts. Declarations of types and functions are very concise.
Scala allows function names to include non-alphanumeric characters. Combined with some syntactic sugar, this feature permits the user to define methods that look and behave like operators. As a result, libraries outside the core of the language can feel “native” to users.
- A sophisticated type system
Scala extends the type system of Java with more flexible generics and a number of more advanced typing constructs. The type system can be intimidating at first, but most of the time you won’t need to worry about the advanced constructs. Type inference helps by automatically inferring type signatures, so that the user doesn’t have to provide trivial type information manually. When you need them, though, the advanced type features provide you with greater flexibility for solving design problems in a type-safe way.
- Scalable—architectures
Scala is designed to scale from small, interpreted scripts to large, distributed applications. Scala provides four language mechanisms that promote scalable composition of systems: 1) explicit self types; 2) abstract type members and generics; 3) nested classes; and 4) mixin composition using traits.
No other language provides all these mechanisms. Together, they allow applications to be constructed from reusable “components” in a type-safe and succinct manner. As we will see, many common design patterns and architectural techniques like dependency injection are easy to implement in Scala without the boilerplate code or lengthy XML configuration files that can make Java development tedious.
- Scalable—performance
Because Scala code runs on the JVM and the CLR, it benefits from all the performance optimizations provided by those runtimes and all the third-party tools that support performance and scalability, such as profilers, distributed cache libraries, clustering mechanisms, etc. If you trust Java’s and C#’s performance, you can trust Scala’s performance. Of course, some particular constructs in the language and some parts of the library may perform significantly better or worse than alternative options in other languages. As always, you should profile your code and optimize it when necessary.
It might appear that OOP and FP are incompatible. In fact, a design philosophy of Scala is that OOP and FP are more synergistic than opposed. The features of one approach can enhance the other.
In FP, functions have no side effects and variables are immutable, while in OOP, mutable state and side effects are common, even encouraged. Scala lets you choose the approach that best fits your design problems. Functional programming is especially useful for concurrency, since it eliminates the need to synchronize access to mutable state. However, “pure” FP can be restrictive. Some design problems are easier to solve with mutable objects.
The name Scala is a contraction of the words scalable language. While this suggests that the pronunciation should be scale-ah, the creators of Scala actually pronounce it scah-lah, like the Italian word for “stairs.” The two “a”s are pronounced the same.
Scala was started by
Martin Odersky in 2001. Martin is a professor in the School of Computer
and Communication Sciences at the Ecole Polytechnique Fédérale de
Lausanne (EPFL). He spent his graduate years working in the group headed
by Niklaus Wirth, of Pascal fame. Martin worked on Pizza, an early
functional language on the JVM. He later worked on GJ, a prototype of
what later became Generics in Java, with Philip Wadler of Haskell fame.
Martin was hired by Sun Microsystems to produce the reference
implementation of javac
, the Java compiler that ships
with the Java Developer Kit (JDK) today.
Martin Odersky’s background and experience are evident in the language. As you learn Scala, you come to understand that it is the product of carefully considered design decisions, exploiting the state of the art in type theory, OOP, and FP. Martin’s experience with the JVM is evident in Scala’s elegant integration with that platform. The synthesis it creates between OOP and FP is an excellent “best of both worlds” solution.
The Seductions of Scala
Today, our industry is fortunate to have a wide variety of language options. The power, flexibility, and elegance of dynamically typed languages have made them very popular again. Yet the wealth of Java and .NET libraries and the performance of the JVM and CLR meet many practical needs for enterprise and Internet projects.
Scala is compelling because it feels like a dynamically typed scripting language, due to its succinct syntax and type inference. Yet Scala gives you all the benefits of static typing, a modern object model, functional programming, and an advanced type system. These tools let you build scalable, modular applications that can reuse legacy Java and .NET APIs and leverage the performance of the JVM and CLR.
Scala is a language for professional developers. Compared to languages like Java and Ruby, Scala is a more difficult language to master because it requires competency with OOP, FP, and static typing to use it most effectively. It is tempting to prefer the relative simplicity of dynamically typed languages. Yet this simplicity can be deceptive. In a dynamically typed language, it is often necessary to use metaprogramming features to implement advanced designs. While metaprogramming is powerful, using it well takes experience and the resulting code tends to be hard to understand, maintain, and debug. In Scala, many of the same design goals can be achieved in a type-safe manner by exploiting its type system and mixin composition through traits.
We feel that the extra effort required day to day to use Scala will promote more careful reflection about your designs. Over time, this discipline will yield more coherent, modular, and maintainable applications. Fortunately, you don’t need all of the sophistication of Scala all of the time. Much of your code will have the simplicity and clarity of code written in your favorite dynamically typed language.
An alternative strategy is to combine several, simpler languages, e.g., Java for object-oriented code and Erlang for functional, concurrent code. Such a decomposition can work, but only if your system decomposes cleanly into such discrete parts and your team can manage a heterogeneous environment. Scala is attractive for situations in which a single, all-in-one language is preferred. That said, Scala code can happily coexist with other languages, especially on the JVM or .NET.
Installing Scala
To get up and running as quickly as possible, this section describes how to install the command-line tools for Scala, which are all you need to work with the examples in the book. For details on using Scala in various editors and IDEs, see Integration with IDEs. The examples used in this book were written and compiled using Scala version 2.7.5.final, the latest release at the time of this writing, and “nightly builds” of Scala version 2.8.0, which may be finalized by the time you read this.
We will work with the JVM version of Scala in this book. First, you must have Java 1.4 or greater installed (1.5 or greater is recommended). If you need to install Java, go to http://www.java.com/en/download/manual.jsp and follow the instructions to install Java on your machine.
The official Scala website is http://www.scala-lang.org/. To install Scala, go to the downloads page. Download the installer for your environment and follow the instructions on the downloads page.
The easiest cross-platform
installer is the IzPack installer. Download the Scala
JAR file, either scala-2.7.5.final-installer.jar or
scala-2.8.0.N-installer.jar, where
N
is the latest release of the 2.8.0 version.
Go to the download directory in a terminal window, and install Scala with the
java
command. Assuming you downloaded scala-2.8.0.final-installer.jar,
run the following command, which will guide you through the
process:
java -jar scala-2.8.0.final-installer.jar
Tip
On Mac OS X, the easiest route to a working Scala installation is
via MacPorts. Follow the installation instructions at http://www.macports.org/, then sudo port install
scala
. You’ll be up and running in a few minutes.
Throughout this book, we
will use the symbol scala-home
to refer to the
“root” directory of your Scala installation.
Note
On Unix, Linux, and Mac OS X systems, you will need to run this
command as the
root
user or using the sudo
command if you want to install
Scala under a system directory, e.g.,
scala-home
=
/usr/local/scala-2.8.0.final.
As an alternative, you can
download and expand the compressed TAR file (e.g.,
scala-2.8.0.final.tgz) or ZIP file
(scala-2.8.0.final.zip). On Unix-like systems, expand
the compressed file into a location of your choosing. Afterward, add the
scala-home
/bin
subdirectory in the new directory to your PATH
. For
example, if you installed into /usr/local/scala-2.8.0.final, then add
/usr/local/scala-2.8.0.final/bin to your
PATH
.
To test your installation, run the following command on the command line:
scala -version
We’ll learn more about the
scala
command-line tool later. You should get something
like the following output:
Scala code runner version 2.8.0.final -- Copyright 2002-2009, LAMP/EPFL
Of course, the version
number you see will be different if you installed a different release.
From now on, when we show command output that contains the version number,
we’ll show it as version 2.8.0.final
.
Congratulations,
you have installed Scala! If you get an error message along the lines of
scala: command not found
, make sure your environment’s
PATH
is set properly to include the correct
bin directory.
Note
Scala versions 2.7.X and earlier are compatible with JDK 1.4 and
later. Scala version 2.8 drops 1.4 compatibility. Note that Scala uses
many JDK classes as its own, for example, the String
class. On .NET, Scala uses the corresponding .NET classes.
You can also find downloads for the API documentation and the sources for Scala itself on the same downloads page.
For More Information
As you explore Scala, you will find other useful resources that are available on http://scala-lang.org. You will find links for development support tools and libraries, tutorials, the language specification [ScalaSpec2009], and academic papers that describe features of the language.
The documentation for the
Scala tools and APIs are especially useful. You can browse the API at
http://www.scala-lang.org/docu/files/api/index.html.
This documentation was generated using the scaladoc
tool, analogous to Java’s javadoc
tool. See The scaladoc Command-Line Tool for more information.
You can also download a
compressed file of the API documentation for local browsing using the
appropriate link on the downloads page, or you
can install it with the sbaz
package tool, as
follows:
sbaz install scala-devel-docs
sbaz
is
installed in the same bin directory as the
scala
and scalac
command-line tools.
The installed documentation also includes details on the scala tool chain
(including sbaz
) and code examples. For more
information on the Scala command-line tools and other resources, see Chapter 14.
A Taste of Scala
It’s time to whet your appetite with some real Scala code. In the following examples, we’ll describe just enough of the details so you understand what’s going on. The goal is to give you a sense of what programming in Scala is like. We’ll explore the details of the features in subsequent chapters.
For our first example, you could run it one of two ways: interactively, or as a “script.”
Let’s start with the
interactive mode. Start the scala interpreter by typing
scala
and the return key on your command line. You’ll
see the following output. (Some of the version numbers may vary.)
Welcome to Scala version 2.8.0.final (Java ...). Type in expressions to have them evaluated. Type :help for more information. scala>
The last line is the prompt
that is waiting for your input. The interactive mode of the
scala
command is very convenient for experimentation
(see The scala Command-Line Tool for more details). An
interactive interpreter like this is called a REPL: Read,
Evaluate, Print, Loop.
Type in the following two lines of code:
val book = "Programming Scala" println(book)
The actual input and output should look like the following:
scala> val book = "Programming Scala" book: java.lang.String = Programming Scala scala> println(book) Programming Scala scala>
The first line uses the
val
keyword to declare a read-only variable named
book
. Note that the output returned from the
interpreter shows you the type and value of book
. This
can be very handy for understanding complex declarations. The second line
prints the value of book
, which is “Programming
Scala”.
Tip
Experimenting with the scala
command in the
interactive mode (REPL) is a great way to learn the details of
Scala.
Many of the examples in
this book can be executed in the interpreter like this. However, it’s
often more convenient to use the second option we mentioned, writing Scala
scripts in a text editor or IDE and executing them with the same
scala
command. We’ll do that for most of the remaining
examples in this chapter.
In your text editor of choice, save the Scala code in the following example to a file named upper1-script.scala in a directory of your choosing:
// code-examples/IntroducingScala/upper1-script.scala
class
Upper
{def
upper
(strings:String
*):Seq[String]
= { strings.map((s:String
)=>
s.toUpperCase()) } }val
up =new
Upper
Console.println(up.upper("A"
,"First"
,"Scala"
,"Program"
))
This Scala script converts strings to uppercase.
By the way, that’s a comment
on the first line (with the name of the source file for the code example).
Scala follows the same comment conventions as Java, C#, C++, etc. A
//
goes to the end
of a line, while a comment
/*
can cross line boundaries.comment
*/
To run this script, go to a command window, change to the same directory, and run the following command:
scala upper1-script.scala
The file is interpreted, meaning it is compiled and executed in one step. You should get the following output:
Array
(A
,FIRST
,SCALA
,PROGRAM
)
In the current example, the
upper
method in the Upper
class (no
pun intended) converts the input strings to uppercase and returns them in
an array. The last line in the example converts four strings and prints
the resulting Array
.
Let’s examine the code in detail, so we can begin to learn Scala syntax. There are a lot of details in just six lines of code! We’ll explain the general ideas here. All the ideas used in this example will be explained more thoroughly in later sections of the book.
In the example, the
Upper
class begins with the class
keyword. The class body is inside the outermost curly braces
({...}
).
The upper
method definition begins on the second line with the
def
keyword, followed by the method name and an
argument list, the return type of the method, an equals sign
(=
), and then the method body.
The argument list in
parentheses is actually a variable-length argument
list of String
s, indicated by the
String*
type following the colon. That is, you can pass
in as many comma-separated strings as you want (including an empty list).
These strings are stored in a parameter named strings
.
Inside the method, strings
is actually an
Array
.
Note
When explicit type information for variables is written in the
code, these type annotations follow the colon after
the item name (i.e., Pascal-like syntax). Why doesn’t Scala follow Java
conventions? Recall that type information is often
inferred in Scala (unlike Java), meaning we don’t
always show type annotations explicitly. Compared to Java’s type item
convention, the item:
type
convention is easier for the compiler to analyze
unambiguously when you omit the colon and the type annotation and just
write item
.
The method return type
appears after the argument list. In this case, the return type is
Seq[String]
, where Seq
(“sequence”)
is a particular kind of collection. It is a parameterized
type (like a generic type in Java),
parameterized here with String
. Note that Scala uses
square brackets ([...]
) for parameterized types,
whereas Java uses angle brackets
(<...>
).
Note
Scala allows angle brackets to be used in method names, e.g.,
naming a “less than” method <
is common. So, to
avoid ambiguities, Scala uses square brackets instead for parameterized
types. They can’t be used in method names. Allowing
<
and >
in method names is
why Scala doesn’t follow Java’s convention for angle brackets.
The body of the
upper
method comes after the equals sign
(=
). Why an equals sign? Why not just curly braces
({...}
), like in Java? Because semicolons, function
return types, method arguments lists, and even the curly braces are
sometimes omitted, using an equals sign prevents several possible parsing
ambiguities. Using an equals sign also reminds us that even functions are
values in Scala, which is consistent with Scala’s support of
functional programming, described in more detail in
Chapter 8.
The method body calls the
map
method on the strings
array,
which takes a function literal as an argument.
Function literals are “anonymous” functions. They are similar to
lambdas, closures,
blocks, or procs in other
languages. In Java, you would have to use an anonymous inner class here
that implements a method defined by an interface, etc.
In this case, we passed in the following function literal:
(s:String
)=>
s.toUpperCase()
It takes an argument list
with a single String
argument named
s
. The body of the function literal is after the
“arrow,” =>
. It calls
toUpperCase()
on s
. The result of
this call is returned by the function literal. In Scala, the last
expression in a function is the return value,
although you can have return
statements elsewhere, too.
The return
keyword is optional here and is rarely used,
except when returning out of the middle of a block (e.g., in an
if
statement).
Note
The value of the last expression is the default return value of a
function. No return
is required.
So, map
passes each String
in strings
to the
function literal and builds up a new collection with the results returned
by the function literal.
To exercise the code, we
create a new Upper
instance and assign it to a variable
named up
. As in Java, C#, and similar languages, the
syntax new Upper
creates a new instance. The
up
variable is declared as a read-only “value” using
the val
keyword.
Finally, we call the
upper
method on a list of strings, and print out the
result with Console.println(...)
, which is equivalent
to Java’s System.out.println(...)
.
We can actually simplify our script even further. Consider this simplified version of the script:
// code-examples/IntroducingScala/upper2-script.scala
object
Upper
{def
upper
(strings:String
*) = strings.map(_
.toUpperCase()) } println(Upper.upper("A"
,"First"
,"Scala"
,"Program"
))
This code does exactly the same thing, but with a third fewer characters.
On the first line,
Upper
is now declared as an object
,
which is a singleton. We are declaring a class, but
the Scala runtime will only ever create one instance of
Upper
. (You can’t write new Upper
,
for example.) Scala uses objects
for situations where
other languages would use “class-level” members, like
static
s in Java. We don’t really need more than one
instance here, so a singleton is fine.
Note
Why doesn’t Scala support static
s? Since
everything is an object in Scala, the
object
construct keeps this policy consistent. Java’s
static
methods and fields are not tied to an actual
instance.
Note that this code is
fully thread-safe. We don’t declare any variables that might cause
thread-safety issues. The API methods we use are also thread-safe.
Therefore, we don’t need multiple instances. A singleton
object
works fine.
The implementation of
upper
on the second line is also simpler. Scala can
usually infer the return type of the method (but not the types of the
method arguments), so we drop the explicit declaration. Also, because
there is only one expression in the method body, we drop the braces and
put the entire method definition on one line. The equals sign before the
method body tells the compiler, as well as the human reader, where the
method body begins.
We have also exploited a shorthand for the function literal. Previously we wrote it as follows:
(s:String
)=>
s.toUpperCase()
We can shorten it to the following expression:
_
.toUpperCase()
Because
map
takes one argument, a function, we can use the
“placeholder” indicator _
instead of a named parameter.
That is, the _
acts like an anonymous variable, to
which each string will be assigned before toUpperCase
is called. Note that the String
type is inferred for
us, too. As we will see, Scala uses _
as a “wildcard”
in several contexts.
You can also use this shorthand syntax in some more complex function literals, as we will see in Chapter 3.
On the last line, using an
object
rather than a class
simplifies the code. Instead of creating an instance with new
Upper
, we can just call the upper
method on
the Upper
object directly (note how this looks like the
syntax you would use when calling static methods in a Java class).
Finally, Scala automatically
imports many methods for I/O, like println
, so we don’t
need to call Console.println()
. We can just use
println
by itself. (See The Predef Object
for details on the types and methods that are automatically imported or
defined.)
Let’s do one last refactoring. Convert the script into a compiled, command-line tool:
// code-examples/IntroducingScala/upper3.scala
object
Upper
{def
main
(args:Array[String]
) = { args.map(_
.toUpperCase()).foreach(printf("%s "
,_
)) println(""
) } }
Now the
upper
method has been renamed main
.
Because Upper
is an object
, this
main
method works exactly like a static
main
method in a Java class. It is the entry point to the
Upper
application.
Note
In Scala, main
must be a method in an
object
. (In Java, main
must be a
static
method in a class
.) The
command-line arguments for the application are passed to
main
in an array of strings, e.g., args:
Array[String]
.
The first line inside the
main
method uses the same shorthand notation for
map
that we just examined:
args.map(_
.toUpperCase())...
The call to
map
returns a new collection. We iterate through it
with foreach
. We use a _
placeholder
shortcut again in another function literal that we
pass to foreach
. In this case, each string in the
collection is passed as an argument to
printf
:
...foreach(printf("%s "
,_
))
To be clear, these two uses
of _
are completely independent of each other. Method
chaining and function-literal shorthands, as in this example, can take
some getting used to, but once you are comfortable with them, they yield
very readable code with minimal use of temporary variables.
The last line in
main
adds a final line feed to the output.
This time, you must first
compile the code to a JVM .class file using
scalac
:
scalac upper3.scala
You should now have a file named Upper.class, just as if you had just compiled a Java class.
Note
You may have noticed that the compiler did not complain when the
file was named
upper3.scala and the object
was
named Upper
. Unlike Java, the file name doesn’t have to match
the name of the type with public
scope. (We’ll
explore the visibility rules in Visibility Rules.) In
fact, unlike Java, you can have as many public types in a single file as
you want. Furthermore, the directory location of a file doesn’t have to
match the package declaration. However, you can certainly follow the
Java conventions, if you want to.
Now, you can execute this command for any list of strings. Here is an example:
scala -cp . Upper Hello World!
The -cp .
option adds the current directory to the search “class path.” You should
get the following output:
HELLO WORLD!
Therefore, we have met the requirement that a programming language book must start with a “hello world” program.
A Taste of Concurrency
There are many reasons to be seduced by Scala. One reason is the Actors API included in the Scala library, which is based on the robust Actors concurrency model built into Erlang (see [Haller2007]). Here is an example to whet your appetite.
In the Actor model of concurrency ([Agha1987]), independent software entities called Actors share no state information with each other. Instead, they communicate by exchanging messages. By eliminating the need to synchronize access to shared, mutable state, it is far easier to write robust, concurrent applications.
In this example, instances
in a geometric Shape
hierarchy are sent to an Actor for
drawing on a display. Imagine a scenario where a rendering “farm”
generates scenes in an animation. As the rendering of a scene is
completed, the shape “primitives” that are part of the scene are sent to
an Actor for a display subsystem.
To begin, we define a
Shape
class hierarchy:
// code-examples/IntroducingScala/shapes.scala
package
shapes {class
Point
(val
x:Double
,val
y:Double
) {override
def
toString
() ="Point("
+ x +","
+ y +")"
}abstract
class
Shape
() {def
draw
():Unit
}class
Circle
(val
center:Point
,val
radius:Double
)extends
Shape
{def
draw
() = println("Circle.draw: "
+this
)override
def
toString
() ="Circle("
+ center +","
+ radius +")"
}class
Rectangle
(val
lowerLeft:Point
,val
height:Double
,val
width:Double
)extends
Shape
{def
draw
() = println("Rectangle.draw: "
+this
)override
def
toString
() ="Rectangle("
+ lowerLeft +","
+ height +","
+ width +")"
}class
Triangle
(val
point1:Point
,val
point2:Point
,val
point3:Point
)extends
Shape
{def
draw
() = println("Triangle.draw: "
+this
)override
def
toString
() ="Triangle("
+ point1 +","
+ point2 +","
+ point3 +")"
} }
The Shape
class hierarchy is defined in a shapes
package. You can
declare the package using Java syntax, but Scala also supports a syntax
similar to C#’s “namespace” syntax, where the entire declaration is scoped
using curly braces, as used here. The Java-style package declaration
syntax is far more commonly used, however, being both compact and
readable.
The Point
class represents a two-dimensional point on a plane. Note the argument
list after the class name. Those are constructor parameters. In Scala, the
whole class body is the constructor, so you list the
arguments for the primary constructor after the class
name and before the class body. (We’ll see how to define auxiliary
constructors in Constructors in Scala.) Because we put the
val
keyword before each parameter declaration, they are
automatically converted to read-only fields with the same names with
public reader methods of the same name. That is, when you instantiate a
Point
instance, e.g., point
, you can
read the fields using point.x
and
point.y
. If you want mutable
fields, then use the keyword var
. We’ll explore
variable declarations and the val
and
var
keywords in Variable Declarations.
The body of
Point
defines one method, an
override of the familiar toString
method in Java (like ToString
in C#). Note that Scala,
like C#, requires the override
keyword whenever you
override a concrete method. Unlike C#, you don’t have to use a virtual
keyword on the original concrete
method. In fact, there is no virtual
keyword in Scala.
As before, we omit the curly braces ({...}
) around the
body of toString
, since it has only one
expression.
Shape
is
an abstract class. Abstract classes in Scala are similar to those in Java
and C#. We can’t instantiate instances of abstract classes, even when all
their field and method members are concrete.
In this case, Shape declares
an abstract draw
method. We know
it is abstract because it has no body. No abstract
keyword is required on the method. Abstract methods in Scala are just like
abstract methods in Java and C#. (See Overriding Members of Classes and Traits
for more details.)
The draw
method returns Unit
,
which is a type that is roughly equivalent to void
in
C-derived languages like Java, etc. (See The Scala Type Hierarchy for more details.)
Circle
is
declared as a concrete subclass of Shape
. It defines
the draw
method to simply print a message to the
console. Circle
also overrides
toString
.
Rectangle
is also a concrete subclass of Shape
that defines
draw
and overrides toString
. For
simplicity, we assume it is not rotated relative to the
x and y axes. Hence, all we need
is one point, the lower lefthand point will do, and the height and width
of the rectangle.
Triangle
follows the same pattern. It takes three Points
as its
constructor arguments.
Both draw
methods in Circle
, Rectangle
, and
Triangle
use this
. As in Java and
C#, this
is how an instance refers to itself. In this
context, where this
is the righthand side of a String
concatenation expression (using the plus sign),
this.toString
is invoked implicitly.
Note
Of course, in a real application, you would not implement drawing in “domain model” classes like this, since the implementations would depend on details like the operating system platform, graphics API, etc. We will see a better design approach when we discuss traits in Chapter 4.
Now that we have defined our shapes types, let’s return to Actors. We define an Actor that receives “messages” that are shapes to draw:
// code-examples/IntroducingScala/shapes-actor.scala
package
shapes {import
scala.actors._import
scala.actors.Actor._object
ShapeDrawingActor
extends
Actor
{def
act
() { loop { receive {case
s:Shape => s.draw
()case
"exit"
=>
println("exiting..."
); exitcase
x:Any => println
("Error: Unknown message! "
+ x) } } } } }
The Actor is declared to be
part of the shapes
package. Next, we have two import
statements.
The first import statement
imports all the types in the scala.actors
package. In
Scala, the underscore _
is used the way the star
*
is used in Java.
Note
Because *
is a valid character for a function
name, it can’t be used as the import
wildcard.
Instead, _
is reserved for this purpose.
All the methods and public
fields from Actor
are imported by the second
import
. These are not static
imports
from the Actor
type, as they would be in Java. Rather,
they are imported from an object
that is also named
Actor
. The class
and
object
can have the same name, as we will see in Companion Objects.
Our Actor class definition,
ShapeDrawingActor
, is an object
that
extends Actor
(the type, not the
object
). The act
method is
overridden to do the unique work of the Actor. Because
act
is an abstract method, we don’t need to explicitly
override it with the override
keyword. Our Actor loops
indefinitely, waiting for incoming messages.
During each pass in the
loop, the receive
method is called. It blocks until a
new message arrives. Why is the code after receive
enclosed in curly braces {...}
and not parentheses
(...)
? We will learn later that there are cases where
this substitution is allowed and is quite useful (see Chapter 3). For now, what you need to know is
that the expressions inside the braces constitute a single
function literal that is passed to
receive
. This function literal does a pattern
match on the message instance to decide how to handle the
message. Because of the case
clauses, it looks like a
typical switch statement in Java, for example, and the behavior is very
similar.
The first
case
does a type comparison with the message. (There is
no explicit variable for the message instance in the code; it is
inferred.) If the message is of type Shape
, the first
case
matches. The message instance is cast to a
Shape
and assigned to the variable
s
, and then the draw
method is
called on it.
If the message is not a
Shape
, the second case
is tried. If
the message is the string "exit"
, the Actor prints a
message and terminates execution. Actors should usually have a way to exit
gracefully!
The last
case
clause handles any other message instance, thereby
functioning as the default case. The Actor reports an
error and then drops the message. Any
is the parent of
all types in the Scala type hierarchy, like Object
is
the root type in Java and other languages. Hence, this case clause will
match any message of any type. Pattern matching is eager; we have to put
this case clause at the end, so it doesn’t consume the messages we are
expecting!
Recall that we declared
draw
as an abstract method in Shape
and we implemented draw
in the concrete subclasses.
Hence, the code in the first case
statement invokes a
polymorphic operation.
Finally, here is a script
that uses the ShapeDrawingActor
Actor:
// code-examples/IntroducingScala/shapes-actor-script.scala
import
shapes._ ShapeDrawingActor.start()ShapeDrawingActor
!new
Circle
(new
Point
(0.0
,0.0
),1.0
)ShapeDrawingActor
!new
Rectangle
(new
Point
(0.0
,0.0
),2
,5
)ShapeDrawingActor
!new
Triangle
(new
Point
(0.0
,0.0
),new
Point
(1.0
,0.0
),new
Point
(0.0
,1.0
))ShapeDrawingActor
!3.14159
ShapeDrawingActor
!"exit"
The shapes in the
shapes
package are imported.
The
ShapeDrawingActor
Actor is started. By default, it runs
in its own thread (there are alternatives we will discuss in Chapter 9), waiting for messages.
Five messages are sent to
the Actor, using the syntax actor ! message
. The first
message sends a Circle
instance. The Actor “draws” the
circle. The second message sends a Rectangle
message.
The Actor “draws” the rectangle. The third message does the same thing for
a Triangle
. The fourth message sends a
Double
that is approximately equal to
Pi. This is an unknown message for the Actor, so it
just prints an error message. The final message sends an “exit” string,
which causes the Actor to terminate.
To try out the Actor example, start by compiling the first two files. You can get the sources from the O’Reilly download site (see Getting the Code Examples for details), or you can create them yourself.
Use the following command to compile the files:
scalac shapes.scala shapes-actor.scala
While the source file names and locations don’t have to match the file contents, you will notice that the generated class files are written to a shapes directory and there is one class file for each class we defined. The class file names and locations must conform to the JVM requirements.
Now you can run the script to see the Actor in action:
scala -cp . shapes-actor-script.scala
You should see the following output:
Circle.draw: Circle(Point(0.0,0.0),1.0) Rectangle.draw: Rectangle(Point(0.0,0.0),2.0,5.0) Triangle.draw: Triangle(Point(0.0,0.0),Point(1.0,0.0),Point(0.0,1.0)) Error: Unknown message! 3.14159 exiting...
For more on Actors, see Chapter 9.
Get Programming Scala now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.