Topics in This Chapter A1
In this chapter, you will learn how to use Scala as an industrial-strength pocket calculator, working interactively with numbers and arithmetic operations. We introduce a number of important Scala concepts and idioms along the way. You will also learn how to browse the Scaladoc documentation at a beginner’s level.
Highlights of this introduction are:
Using the Scala interpreter
Defining variables with var
and val
Numeric types
Using operators and functions
Navigating Scaladoc
Depending on how you installed Scala, you can run the Scala intepreter from the command line or from your integrated development environment. Since the installation instructions change ever so often, I put a set of instructions on the site http://horstmann.com/scala
.
Start the interpreter and type commands followed by Enter. Each time, the interpreter displays the answer, as shown in Figure 1–1. For example, if you type 8 * 5 + 2
(as shown in boldface below), you get 42
.
scala> 8 * 5 + 2
val res0: Int = 42
The answer is given the name res0
. You can use that name in subsequent computations:
scala> 0.5 * res0
val res1: Double = 21.0
scala> "Hello, " + res0
val res2: String = Hello, 42
As you can see, the interpreter also displays the type of the result—in our examples, Int
, Double
, and String
.
Tip
Don’t like the command shell? Several integrated development environments that support Scala have a “worksheet” feature for entering expressions and displaying their result whenever the sheet is saved. Figure 1–2 shows a worksheet in Visual Studio Code. An online version is at https://scastie.scala-lang.org
.
When calling methods, try using tab completion for method names. Type res2.to
and then press the Tab key. If the interpreter offers choices such as
toCharArray toLowerCase toString toUpperCase
this means tab completion works in your environment. Type a U
and press the Tab key again. You now get a single completion:
res2.toUpperCase
Press the Enter key, and the answer is displayed. (If you can’t use tab completion in your environment, you’ll have to type the complete method name yourself.)
Also try pressing the ↑ and ↓ arrow keys. In most implementations, you will see the previously issued commands, and you can edit them. Use the ←, →, and Del keys to change the last command to
res2.toLowerCase
As you can see, the Scala interpreter reads an expression, evaluates it, prints it, and reads the next expression. This is called the read-eval-print loop, or REPL.
Technically speaking, the scala
program is not an interpreter. Behind the scenes, your input is quickly compiled into bytecode, and the bytecode is executed by the Java virtual machine. For that reason, most Scala programmers prefer to call it “the REPL.”
Tip
The REPL is your friend. Instant feedback encourages experimenting, and you will feel good whenever something works.
It is a good idea to keep an editor window open at the same time, so you can copy and paste successful code snippets for later use. Also, as you try more complex examples, you may want to compose them in the editor and then paste them into the REPL.
Tip
In the REPL, type :help
to see a list of commands. All commands start with a colon. For example, the :type
command gives the type of an expression. You only have to enter the unique prefix of each command. For example, :t
is the same as :type
—at least for now, since there isn’t currently another command starting with t
.
Instead of using res0
, res1
, and so on, you can define your own names:
scala> val answer = 8 * 5 + 2
answer: Int = 42
You can use these names in subsequent expressions:
scala> 0.5 * answer
res3: Double = 21.0
A value declared with val
is actually a constant—you can’t change its contents:
scala> answer = 0
-- Error:
1 |answer = 0
|^^^^^^^^^^
|Reassignment to val answer
To declare a variable whose contents can vary, use a var
:
var counter = 0
counter = 1 //
OK, can change avar
In Scala, you are encouraged to use a val
unless you really need to change the contents. Perhaps surprisingly for Java, Python, or C++ programmers, most programs don’t need many var
variables.
Note that you need not specify the type of a value or variable. It is inferred from the type of the expression with which you initialize it. (It is an error to declare a value or variable without initializing it.)
However, you can specify the type if necessary. For example,
val message: String = null
val greeting: Any = "Hello"
Note
In Scala, the type of a variable or function is written after the name of the variable or function. This makes it easier to read declarations with complex types.
I frequently move back and forth between Scala and Java. I find that my fingers write Java declarations such as String greeting
on autopilot, so I have to rewrite them as greeting: String
. This is a bit annoying, but when I work with complex Scala programs, I really appreciate that I don’t have to decrypt C-style type declarations.
Note
You may have noticed that there were no semicolons after variable declarations or assignments. In Scala, semicolons are only required if you have multiple statements on the same line.
You can declare multiple values or variables together:
val xmax, ymax = 100 //
Sets xmax and ymax to 100var prefix, suffix: String = null
//
prefix and suffix are both strings, initialized with null
Note
In Scala, integer hexadecimal literals start with 0x
, such as 0xCAFEBABE
. There are no octal or binary literals. Long integer literals end in an L
. Number literals can have underscores for the benefit of the human reader, such as 10_000_000_000L
.
You have already seen some of the data types of Scala, such as Int
and Double
. Scala has seven numeric types: Byte
, Char
, Short
, Int
, Long
, Float
, and Double
, and a Boolean
type. In Scala, these types are classes. There is no distinction between primitive types and class types in Scala. You can invoke methods on numbers, for example:
1.toString() //
Yields the string "1"
or, more excitingly,
1.to(10) //
Yields Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
(We will discuss the Range
class in Chapter 13. For now, just view it as a collection of numbers.)
In Scala, there is no need for wrapper types. It is the job of the Scala compiler to convert between primitive types and wrappers. For example, if you make an array of Int
, you get an int[]
array in the virtual machine.
As you saw in Section 1.1, “The Scala Interpreter,” on page 1, Scala relies on the underlying java.lang.String
class for strings. However, it augments that class with well over a hundred operations in the StringOps
class. For example, the intersect
method yields the characters that are common to two strings:
"Hello".intersect("World") //
Yields "lo"
In this expression, the java.lang.String
object "Hello"
is implicitly converted to a StringOps
object, and then the intersect
method of the StringOps
class is applied, as if you had written
scala.collection.StringOps("Hello").intersect("World")
So, remember to look into the StringOps
class when you use the Scala documentation (see Section 1.7, “Scaladoc” on page 11).
Similarly, there are classes RichInt
, RichDouble
, RichChar
, and so on. Each of them has a small set of convenience methods for acting on their poor cousins—Int
, Double
, or Char
. The to
method that you saw above is actually a method of the RichInt
class. The expression
1.to(10)
is equivalent to
scala.runtime.RichInt(1).to(10)
The Int
value 1
is first converted to a RichInt
, and the to
method is applied to that value.
Finally, there are classes BigInt
and BigDecimal
for computations with an arbitrary (but finite) number of digits. These are backed by the java.math.BigInteger
and java.math.BigDecimal
classes, but, as you will see in the next section, they are much more convenient because you can use them with the usual mathematical operators.
Note
In Scala, you use methods, not casts, to convert between numeric types. For example, 99.44.toInt
is 99
, and 99.toChar
is 'c'
. The toString
method converts any object to a string.
To convert a string containing a number into the number, use toInt
or toDouble
. For example, "99.44".toDouble
is 99.44
.
Arithmetic operators in Scala work just as you would expect:
val answer = 8 * 5 + 2
The + - * / %
operators do their usual job, as do the bit operators & | ^ << >> >>>
. There is just one surprising aspect: These operators are actually methods. For example,
a + b
is a shorthand for
a.+(b)
Here, +
is the name of the method. Scala has no silly prejudice against non-alphanumeric characters in method names. You can define methods with just about any symbols for names. For example, the BigInt
class defines a method called /%
that returns a pair containing the quotient and remainder of a division.
In general, you can write
a
methodb
as a shorthand for
a.
method(b)
where method is a method with two arguments: the “receiver” a
and the explicit argument b
. For example, instead of
1.to(10)
you can write
1 to 10
Use whatever you think is easier to read. Beginning Scala programmers tend to stick to the dot notation, and that’s fine. Of course, just about everyone prefers a + b
over a.+(b)
.
Unlike Java, JavaScript, or C++, Scala does not have ++
or --
operators. Instead, simply use +=1
or -=1
:
counter+=1 //
Increments counter—Scala has no ++
Some people wonder if there is any deep reason for Scala’s refusal to provide a ++
operator. (Note that you can’t simply implement a method called ++
. Since the Int
class is immutable, such a method cannot change an integer value.) The Scala designers decided it wasn’t worth having yet another special rule just to save one keystroke.
You can use the usual mathematical operators with BigInt
and BigDecimal
objects:
val x: BigInt = 1234567890
x * x * x //
Yields 1881676371789154860897069000
That’s much better than Java, where you would have had to call x.multiply(x).multiply(x)
.
Note
In Java, you cannot overload operators, and the Java designers claimed this is a good thing because it stops you from inventing crazy operators like !@$&*
that would make your program impossible to read. Of course, that’s silly; you can make your programs just as unreadable by using crazy method names like qxywz
. Scala allows you to define operators, leaving it up to you to use this feature with restraint and good taste.
You have already seen how to call methods on objects, such as
"Hello".intersect("World")
Methods without parameters are often invoked without parentheses. For example, the API of the scala.collection.StringOps
class shows a method sorted
, without ()
, which yields a new string with the letters in sorted order. Call it as
"Bonjour".sorted //
Yields the string "Bjnooru"
The rule of thumb is that a parameterless method that doesn’t modify the object has no parentheses. We discuss this further in Chapter 5.
In Java, mathematical functions such as sqrt
are defined as static methods of the Math
class. In Scala, you define such methods in singleton objects, which we will discuss in detail in Chapter 6. A package can have a package object. In that case, you can import the package and use the methods of the package object without any prefix:
import scala.math.*
sqrt(2) //
Yields 1.4142135623730951pow(2, 4) //
Yields 16.0min(3, Pi) //
Yields 3.0
If you don’t import the scala.math
package, add the package name:
scala.math.sqrt(2)
Note
If a package name starts with scala.
, you can omit the scala
prefix. For example, import math.*
is equivalent to import scala.math.*
, and math.sqrt(2)
is the same as scala.math.sqrt(2)
. However, in this book, I always use the scala
prefix for clarity.
You can find more information about the import
statement in Chapter 7. For now, just use import packageName.*
whenever you need to import a particular package.
Often, a class has a companion object providing methods that don’t operate on instances. For example, the BigInt
companion object to the scala.math.BigInt
class declares probablePrime
, which does not operate on a BigInt
. Instead, it generates a random prime BigInt
with a given number of bits:
BigInt.probablePrime(100, scala.util.Random)
Here, Random
is a singleton random number generator object, defined in the scala.util
package. Try this in the REPL; you’ll get a number such as 1039447980491200275486540240713
.
apply
MethodIn Scala, it is common to use a syntax that looks like a function call. For example, if s
is a string, then s(i)
is the i
th character of the string. (In C++, JavaScript, and Python, you would write s[i]
; in Java, s.charAt(i)
.) Try it out in the REPL:
val s = "Hello"
s(4) //
Yields 'o'
You can think of this as an overloaded form of the ()
operator. It is implemented as a method with the name apply
. For example, in the documentation of the StringOps
class, you will find a method
def apply(i: Int): Char
That is, s(4)
is a shortcut for
s.apply(4)
Why not use the []
operator? You can think of a sequence s of element type T
as a function from { 0, 1,..., n – 1 } to T
that maps i to s(i), the ith element of the sequence.
This argument is even more convincing for maps. As you will see in Chapter 4, you look up a map value for a given key as map(key)
. Conceptually, a map is a function from keys to values, and it makes sense to use the function notation.
Caution
Occasionally, the ()
notation conflicts with another Scala feature: “contextual” parameters. For example, the expression
"Bonjour".sorted(3)
yields an error because the sorted
method can optionally be called with an ordering, but 3
is not a valid ordering. You can use another variable:
val result = "Bonjour".sorted
result(3)
or call apply
explicitly:
"Bonjour".sorted.apply(3)
When you look at the documentation for the BigInt
companion object, you will see apply
methods that let you convert strings or numbers to BigInt
objects. For example, the call
BigInt("1234567890")
is a shortcut for
BigInt.apply("1234567890")
It yields a new BigInt
object, without having to use new
. For example:
BigInt("1234567890") * BigInt("112358111321")
Using the apply
method of a companion object is a common Scala idiom for constructing objects. For example, Array(1, 4, 9, 16)
returns an array, thanks to the apply
method of the Array
companion object.
Note
All through this chapter, we have assumed that Scala code is executed on the Java virtual machine. That is in fact true for the standard Scala distribution. However, the Scala.js project (https://www.scala-js.org
) provides tools to translate Scala to JavaScript. If you take advantage of that project, you can write both the client-side and the server-side code of web applications in Scala.
Use Scaladoc to navigate the Scala API (see Figure 1–3).
Using Scaladoc can be a bit overwhelming. Scala classes tend to have many convenience methods. Some methods use advanced features that are more meaningful to library implementors than to library users.
Here are some tips for navigating Scaladoc as a newcomer to the language.
You can browse Scaladoc online at https://scala-lang.org/api/3.x
, but it is a good idea to download a copy and install it locally.
Scaladoc is organized by packages. However, if you know a class or method name, don’t bother navigating to the package. Simply use the search bar on the top of the entry page (see Figure 1–4).
Then click on a matching class or method (Figure 1–5).
Note the C and O symbols next to the class name. They let you navigate to the class (C) or the companion object (O). For traits (which are similar to Java interfaces and described in Chapter 10), you see t and O symbols instead.
Keep these tips in mind:
Remember to look into RichInt
, RichDouble
, and so on, if you want to know how to work with numeric types. Similarly, to work with strings, look into StringOps
.
The mathematical functions are in the package scala.math
, not in any class.
Sometimes, you’ll see methods with funny names. For example, BigInt
has a method unary_-
. As you will see in Chapter 11, this is how you define the prefix negation operator -x
.
Methods can have functions as parameters. For example, the count
method in StringOps
requires a function that returns true
or false
for a Char
, specifying which characters should be counted:
def count(p: (Char) => Boolean) : Int
You supply a function, often in a very compact notation, when you call the method. As an example, the call s.count(_.isUpper)
counts the number of uppercase characters. We will discuss this style of programming in much more detail in Chapter 12.
You’ll occasionally run into classes such as Range
or Seq[Char]
. They mean what your intuition tells you—a range of numbers, a sequence of characters. You will learn all about these classes as you delve more deeply into Scala.
In Scala, you use square brackets for type parameters. A Seq[Char]
is a sequence of elements of type Char
, and Seq[A]
is a sequence of elements of some type A
.
There are several slightly different types for sequential data structures such as Iterable
, IterableOnce
, IndexedSeq
, LinearSeq
, and so on. The differences between them are not very important for beginners. When you see such a construct, just think “sequence.” For example, the StringOps
class defines a method
def concat(suffix: IterableOnce[Char]): String
The suffix
can be just about any character sequence, since the ability to produce the elements once is very basic. For example, the characters could come from a file or socket. We won’t see for a while how to do that, but here is another example where the characters come from a range:
"bob".concat('c'.to('z')) //
Yields "bobcdefghijklmnopqrstuvwxyz"
Don’t get discouraged that there are so many methods. It’s the Scala way to provide lots of methods for every conceivable use case. When you need to solve a particular problem, just look for a method that is useful. More often than not, there is one that addresses your task, which means you don’t have to write so much code yourself.
Some methods have an “implicit” or “using” parameter. For example, the sorted
method of StringOps
is declared as
def sorted[B >: Char](implicit ord: scala.math.Ordering[B]): String
That means that an ordering is supplied “implicitly,” using a mechanism that we will discuss in detail in Chapter 19. You can ignore implicit
and using
parameters for now.
Finally, don’t worry if you run into the occasional indecipherable incantation, such as the [B >: Char]
in the declaration of sorted
. The expression B >: Char
means “any supertype of Char
,” but for now, ignore that generality.
Whenever you are confused what a method does, just try it out in the REPL:
"Scala".sorted //
Yields "Saacl"
Now you can clearly see that the method returns a new string that consists of the characters in sorted order.
Scaladoc has a query language for finding methods by their parameter and return types, separated by =>
. For example, searching for List[String] => List[String]
yields methods that transform a list of strings into another, such as distinct
, reversed
, sorted
, and tail
.
1. In the Scala REPL, type 3.
followed by the Tab key. What methods can be applied?
2. In the Scala REPL, compute the square root of 3, and then square that value. By how much does the result differ from 3? (Hint: The res
variables are your friend.)
3. What happens if you define a variable res99
in the REPL?
4. Scala lets you multiply a string with a number—try out "crazy" * 3
in the REPL. What does this operation do? Where can you find it in Scaladoc?
5. What does 10 max 2
mean? In which class is the max
method defined?
6. Using BigInt
, compute 21024.
7. What do you need to import so that you can get a random prime as probablePrime(100, Random)
, without any qualifiers before probablePrime
and Random
?
8. One way to create random file or directory names is to produce a random BigInt
and convert it to base 36, yielding a string such as "qsnvbevtomcj38o06kul"
. Poke around Scaladoc to find a way of doing this in Scala.
9. How do you get the first character of a string in Scala? The last character?
10. What do the take
, drop
, takeRight
, and dropRight
string methods do? What advantage or disadvantage do they have over using substring
?
3.147.75.221