Chapter 1. The Basics

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

1.1 The Scala Interpreter

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.

Images

Figure 1–1 The Scala Interpreter

Images 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.

Images

Figure 1–2 A Scala Worksheet

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.”

Images 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.

Images 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.

1.2 Declaring Values and Variables

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 a var

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"

Images 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.

Images 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 100
var prefix, suffix: String = null
  // prefix and suffix are both strings, initialized with null

Images 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.

1.3 Commonly Used Types

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.

Images 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.

1.4 Arithmetic and Operator Overloading

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 method b

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).

Images 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.

1.5 More about Calling Methods

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.4142135623730951
pow(2, 4) // Yields 16.0
min(3, Pi) // Yields 3.0

If you don’t import the scala.math package, add the package name:

scala.math.sqrt(2)

Images 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.

1.6 The apply Method

In 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 ith 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.

Images 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.

Images 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.

1.7 Scaladoc

Use Scaladoc to navigate the Scala API (see Figure 1–3).

Images

Figure 1–3 The entry page for Scaladoc

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).

Images

Figure 1–4 The search bar in Scaladoc

Then click on a matching class or method (Figure 1–5).

Images

Figure 1–5 Class documentation in Scaladoc

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.

Exercises

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?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.197.93