The Artima Developer Community

Chapter 5 of Programming in Scala, First Edition
Basic Types and Operations
by Martin Odersky, Lex Spoon, and Bill Venners
December 10, 2008

Now that you've seen classes and objects in action, it's a good time to look at Scala's basic types and operations in more depth. If you're familiar with Java, you'll be glad to find that Java's basic types and operators have the same meaning in Scala. However there are some interesting differences that will make this chapter worthwhile reading even if you're an experienced Java developer. Because some of the aspects of Scala covered in this chapter are essentially the same in Java, we've inserted notes indicating what Java developers can safely skip, to expedite your progress.

In this chapter, you'll get an overview of Scala's basic types, including Strings and the value types Int, Long, Short, Byte, Float, Double, Char, and Boolean. You'll learn the operations you can perform on these types, including how operator precedence works in Scala expressions. You'll also learn how implicit conversions can "enrich" variants of these basic types, giving you additional operations beyond those supported by Java.

5.1 Some basic types [link]

Several fundamental types of Scala, along with the ranges of values instances of these types may have, are shown in Table 5.1. Collectively, types Byte, Short, Int, Long, and Char are called integral types. The integral types plus Float and Double are called numeric types.

Table 5.1 - Some basic types
Value type
Range
Byte 8-bit signed two's complement integer (-27 to 27 - 1, inclusive)
Short 16-bit signed two's complement integer (-215 to 215 - 1, inclusive)
Int 32-bit signed two's complement integer (-231 to 231 - 1, inclusive)
Long 64-bit signed two's complement integer (-263 to 263 - 1, inclusive)
Char 16-bit unsigned Unicode character (0 to 216 - 1, inclusive)
String a sequence of Chars
Float 32-bit IEEE 754 single-precision float
Double 64-bit IEEE 754 double-precision float
Boolean true or false

Other than String, which resides in package java.lang, all of the types shown in Table 5.1 are members of package scala.[1] For example, the full name of Int is scala.Int. However, given that all the members of package scala and java.lang are automatically imported into every Scala source file, you can just use the simple names (i.e., names like Boolean, Char, or String) everywhere.

Note

You can in fact currently use lower case aliases for Scala value types, which correspond to Java's primitive types. For example, you can say int instead of Int in a Scala program. But keep in mind they both mean exactly the same thing: scala.Int. The recommended style that arose from the experience of the Scala community is to always use the upper case form, which is what we do in this book. In honor of this community-driven choice, the lower case variants may be deprecated or even removed in a future version of Scala, so you would be wise indeed to go with the community flow and say Int, not int, in your Scala code.

Savvy Java developers will note that Scala's basic types have the exact same ranges as the corresponding types in Java. This enables the Scala compiler to transform instances of Scala value types, such as Int or Double, down to Java primitive types in the bytecodes it produces.

5.2 Literals [link]

All of the basic types listed in Table 5.1 can be written with literals. A literal is a way to write a constant value directly in code.

Fast track for Java programmers

The syntax of most literals shown in this section are exactly the same as in Java, so if you're a Java master, you can safely skip much of this section. The two differences you should read about are Scala's literals for raw strings and symbols, which are described starting here.

Integer literals

Integer literals for the types Int, Long, Short, and Byte come in three forms: decimal, hexadecimal, and octal. The way an integer literal begins indicates the base of the number. If the number begins with a 0x or 0X, it is hexadecimal (base 16), and may contain 0 through 9 as well as upper or lowercase digits A through F. Some examples are:

  scala> val hex = 0x5
  hex: Int = 5
  
scala> val hex2 = 0x00FF hex2: Int = 255
scala> val magic = 0xcafebabe magic: Int = -889275714

Note that the Scala shell always prints integer values in base 10, no matter what literal form you may have used to initialize it. Thus the interpreter displays the value of the hex2 variable you initialized with literal 0x00FF as decimal 255. (Of course, you don't need to take our word for it. A good way to start getting a feel for the language is to try these statements out in the interpreter as you read this chapter.) If the number begins with a zero, it is octal (base 8), and may, therefore, only contain digits 0 through 7. Some examples are:

  scala> val oct = 035  // (35 octal is 29 decimal)
  oct: Int = 29
  
scala> val nov = 0777 nov: Int = 511
scala> val dec = 0321 dec: Int = 209

If the number begins with a non-zero digit, and is otherwise undecorated, it is decimal (base 10). For example:

  scala> val dec1 = 31
  dec1: Int = 31
  
scala> val dec2 = 255 dec2: Int = 255
scala> val dec3 = 20 dec3: Int = 20

If an integer literal ends in an L or l, it is a Long, otherwise it is an Int. Some examples of Long integer literals are:

  scala> val prog = 0XCAFEBABEL
  prog: Long = 3405691582
  
scala> val tower = 35L tower: Long = 35
scala> val of = 31l of: Long = 31

If an Int literal is assigned to a variable of type Short or Byte, the literal is treated as if it were a Short or Byte type so long as the literal value is within the valid range for that type. For example:

  scala> val little: Short = 367
  little: Short = 367
  
scala> val littler: Byte = 38 littler: Byte = 38

Floating point literals

Floating point literals are made up of decimal digits, optionally containing a decimal point, and optionally followed by an E or e and an exponent. Some examples of floating-point literals are:

  scala> val big = 1.2345
  big: Double = 1.2345
  
scala> val bigger = 1.2345e1 bigger: Double = 12.345
scala> val biggerStill = 123E45 biggerStill: Double = 1.23E47

Note that the exponent portion means the power of 10 by which the other portion is multiplied. Thus, 1.2345e1 is 1.2345 times 101, which is 12.345. If a floating-point literal ends in an F or f, it is a Float, otherwise it is a Double. Optionally, a Double floating-point literal can end in D or d. Some examples of Float literals are:

  scala> val little = 1.2345F
  little: Float = 1.2345
  
scala> val littleBigger = 3e5f littleBigger: Float = 300000.0

That last value expressed as a Double could take these (and other) forms:

  scala> val anotherDouble = 3e5
  anotherDouble: Double = 300000.0
  
scala> val yetAnother = 3e5D yetAnother: Double = 300000.0

Character literals

Character literals are composed of any Unicode character between single quotes, such as:

  scala> val a = 'A'
  a: Char = A

In addition to providing an explicit character between the single quotes, you can provide an octal or hex number for the character code point preceded by a backslash. The octal number must be between '\0' and '\377'. For example, the Unicode character code point for the letter A is 101 octal. Thus:

  scala> val c = '\101'
  c: Char = A

A character literal can also be given as a general Unicode character consisting of four hex digits and preceded by a \u, as in:

  scala> val d = '\u0041'
  d: Char = A
  
scala> val f = '\u0044' f: Char = D
In fact, such Unicode characters can appear anywhere in a Scala program. For instance you could also write an identifier like this:
  scala> val B\u0041\u0044 = 1
  BAD: Int = 1
This identifier is treated as identical to BAD, the result of expanding the two Unicode characters in the code above. In general, it is a bad idea to name identifiers like this, because it is hard to read. Rather, this syntax is intended to allow Scala source files that include non-ASCII Unicode characters to be represented in ASCII.

Finally, there are also a few character literals represented by special escape sequences, shown in Table 5.2. For example:

  scala> val backslash = '\\'
  backslash: Char = \

Table 5.2 - Special character literal escape sequences
Literal Meaning
\n line feed (\u000A)
\b backspace (\u0008)
\t tab (\u0009)
\f form feed (\u000C)
\r carriage return (\u000D)
\" double quote (\u0022)
\' single quote (\u0027)
\\ backslash (\u005C)

String literals

A string literal is composed of characters surrounded by double quotes:

  scala> val hello = "hello"
  hello: java.lang.String = hello
The syntax of the characters within the quotes is the same as with character literals. For example:
  scala> val escapes = "\\\"\'"
  escapes: java.lang.String = \"'

Because this syntax is awkward for strings that contain a lot of escape sequences or strings that span multiple lines, Scala includes a special syntax for raw strings. You start and end a raw string with three double quotation marks in a row ("""). The interior of a raw string may contain any characters whatsoever, including newlines, quotation marks, and special characters, except of course three quotes in a row. For example, the following program prints out a message using a raw string:

  println("""Welcome to Ultamix 3000.
             Type "HELP" for help.""")
Running this code does not produce quite what is desired, however:

  Welcome to Ultamix 3000.
             Type "HELP" for help.
The issue is that the leading spaces before the second line are included in the string! To help with this common situation, you can call stripMargin on strings. To use this method, put a pipe character (|) at the front of each line, and then call stripMargin on the whole string:
  println("""|Welcome to Ultamix 3000.
             |Type "HELP" for help.""".stripMargin)
Now the code behaves as desired:
  Welcome to Ultamix 3000.
  Type "HELP" for help.

Symbol literals

A symbol literal is written 'ident, where ident can be any alphanumeric identifier. Such literals are mapped to instances of the predefined class scala.Symbol. Specifically, the literal 'cymbal will be expanded by the compiler to a factory method invocation: Symbol("cymbal"). Symbol literals are typically used in situations where you would use just an identifier in a dynamically typed language. For instance, you might want to define a method that updates a record in a database:

  scala> def updateRecordByName(r: Symbol, value: Any) {
           // code goes here
         }
  updateRecordByName: (Symbol,Any)Unit
The method takes as parameters a symbol indicating the name of a record field and a value with which the field should be updated in the record. In a dynamically typed language, you could invoke this operation passing an undeclared field identifier to the method, but in Scala this would not compile:
  scala> updateRecordByName(favoriteAlbum, "OK Computer")
  <console>:6: error: not found: value favoriteAlbum
         updateRecordByName(favoriteAlbum, "OK Computer")
                           ^  

Instead, and almost as concisely, you can pass a symbol literal:

  scala> updateRecordByName('favoriteAlbum"OK Computer")
There is not much you can do with a symbol, except find out its name:
  scala> val s = 'aSymbol
  s: Symbol = 'aSymbol
  
scala> s.name res20: String = aSymbol
Another thing that's noteworthy is that symbols are interned. If you write the same symbol literal twice, both expressions will refer to the exact same Symbol object.

Boolean literals

The Boolean type has two literals, true and false:
  scala> val bool = true 
  bool: Boolean = true
  
scala> val fool = false fool: Boolean = false

That's all there is to it. You are now literally[2] an expert in Scala.

5.3 Operators are methods [link]

Scala provides a rich set of operators for its basic types. As mentioned in previous chapters, these operators are actually just a nice syntax for ordinary method calls. For example, 1 + 2 really means the same thing as (1).+(2). In other words, class Int contains a method named + that takes an Int and returns an Int result. This + method is invoked when you add two Ints:

  scala> val sum = 1 + 2    // Scala invokes (1).+(2)
  sum: Int = 3

To prove this to yourself, you can write the expression explicitly as a method invocation:

  scala> val sumMore = (1).+(2sumMore: Int = 3

In fact, Int contains several overloaded + methods that take different parameter types.[3] For example, Int has another method, also named +, that takes and returns a Long. If you add a Long to an Int, this alternate + method will be invoked, as in:

  scala> val longSum = 1 + 2L    // Scala invokes (1).+(2L)
  longSum: Long = 3
The + symbol is an operator—an infix operator to be specific. Operator notation is not limited to methods like + that look like operators in other languages. You can use any method in operator notation. For example, class String has a method, indexOf, that takes one Char parameter. The indexOf method searches the string for the first occurrence of the specified character, and returns its index or -1 if it doesn't find the character. You can use indexOf as an operator, like this:
  scala> val s = "Hello, world!" 
  s: java.lang.String = Hello, world!
  
scala> s indexOf 'o'     // Scala invokes s.indexOf('o') res0: Int = 4

In addition, String offers an overloaded indexOf method that takes two parameters, the character for which to search and an index at which to start. (The other indexOf method, shown previously, starts at index zero, the beginning of the String.) Even though this indexOf method takes two arguments, you can use it in operator notation. But whenever you call a method that takes multiple arguments using operator notation, you have to place those arguments in parentheses. For example, here's how you use this other indexOf form as an operator (continuing from the previous example):

  scala> s indexOf ('o'5// Scala invokes s.indexOf('o', 5)
  res1: Int = 8

Any method can be an operator

In Scala operators are not special language syntax: any method can be an operator. What makes a method an operator is how you use it. When you write "s.indexOf('o')", indexOf is not an operator. But when you write "s indexOf 'o'", indexOf is an operator, because you're using it in operator notation.

So far, you've seen examples of infix operator notation, which means the method to invoke sits between the object and the parameter or parameters you wish to pass to the method, as in "7 + 2". Scala also has two other operator notations: prefix and postfix. In prefix notation, you put the method name before the object on which you are invoking the method, for example, the `-' in -7. In postfix notation, you put the method after the object, for example, the "toLong" in "7 toLong".

In contrast to the infix operator notation—in which operators take two operands, one to the left and the other to the right—prefix and postfix operators are unary: they take just one operand. In prefix notation, the operand is to the right of the operator. Some examples of prefix operators are -2.0, !found, and ~0xFF. As with the infix operators, these prefix operators are a shorthand way of invoking methods. In this case, however, the name of the method has "unary_" prepended to the operator character. For instance, Scala will transform the expression -2.0 into the method invocation "(2.0).unary_-". You can demonstrate this to yourself by typing the method call both via operator notation and explicitly:

  scala> -2.0                  // Scala invokes (2.0).unary_-
  res2: Double = -2.0
  
scala> (2.0).unary_- res3: Double = -2.0

The only identifiers that can be used as prefix operators are +, -, !, and ~. Thus, if you define a method named unary_!, you could invoke that method on a value or variable of the appropriate type using prefix operator notation, such as !p. But if you define a method named unary_*, you wouldn't be able to use prefix operator notation, because * isn't one of the four identifiers that can be used as prefix operators. You could invoke the method normally, as in p.unary_*, but if you attempted to invoke it via *p, Scala will parse it as if you'd written *.p, which is probably not what you had in mind![4]

Postfix operators are methods that take no arguments, when they are invoked without a dot or parentheses. In Scala, you can leave off empty parentheses on method calls. The convention is that you include parentheses if the method has side effects, such as println(), but you can leave them off if the method has no side effects, such as toLowerCase invoked on a String:

  scala> val s = "Hello, world!"
  s: java.lang.String = Hello, world!
  
scala> s.toLowerCase  res4: java.lang.String = hello, world!

In this latter case of a method that requires no arguments, you can alternatively leave off the dot and use postfix operator notation:

  scala> s toLowerCase
  res5: java.lang.String = hello, world!

In this case, toLowerCase is used as a postfix operator on the operand s.

To see what operators you can use with Scala's basic types, therefore, all you really need to do is look at the methods declared in the type's classes in the Scala API documentation. Given that this is a Scala tutorial, however, we'll give you a quick tour of most of these methods in the next few sections.

Fast track for Java programmers

Many aspects of Scala described in the remainder of this chapter are the same as in Java. If you're a Java guru in a rush, you can safely skip to Section 5.7 here, which describes how Scala differs from Java in the area of object equality.

5.4 Arithmetic operations [link]

You can invoke arithmetic methods via infix operator notation for addition (+), subtraction (-), multiplication (*), division (/), and remainder (%), on any numeric type. Here are some examples:

  scala> 1.2 + 2.3
  res6: Double = 3.5
  
scala> 3 - 1 res7: Int = 2
scala> 'b' - 'a' res8: Int = 1
scala> 2L * 3L   res9: Long = 6
scala> 11 / 4    res10: Int = 2
scala> 11 % 4    res11: Int = 3
scala> 11.0f / 4.0f res12: Float = 2.75
scala> 11.0 % 4.0   res13: Double = 3.0

When both the left and right operands are integral types (Int, Long, Byte, Short, or Char), the / operator will tell you the whole number portion of the quotient, excluding any remainder. The % operator indicates the remainder of an implied integer division.

The floating-point remainder you get with % is not the one defined by the IEEE 754 standard. The IEEE 754 remainder uses rounding division, not truncating division, in calculating the remainder, so it is quite different from the integer remainder operation. If you really want an IEEE 754 remainder, you can call IEEEremainder on scala.Math, as in:

  scala> Math.IEEEremainder(11.04.0)
  res14: Double = -1.0

The numeric types also offer unary prefix operators + (method unary_+) and - (method unary_-), which allow you to indicate a literal number is positive or negative, as in -3 or +4.0. If you don't specify a unary + or -, a literal number is interpreted as positive. Unary + exists solely for symmetry with unary -, but has no effect. The unary - can also be used to negate a variable. Here are some examples:

  scala> val neg = 1 + -3
  neg: Int = -2
  
scala> val y = +3 y: Int = 3
scala> -neg res15: Int = 2

5.5 Relational and logical operations [link]

You can compare numeric types with relational methods greater than (>), less than (<), greater than or equal to (>=), and less than or equal to (<=), which yield a Boolean result. In addition, you can use the unary `!' operator (the unary_! method) to invert a Boolean value. Here are a few examples:

  scala> 1 > 2 
  res16: Boolean = false
  
scala> 1 < 2  res17: Boolean = true
scala> 1.0 <= 1.0   res18: Boolean = true
scala> 3.5f >= 3.6f   res19: Boolean = false
scala> 'a' >= 'A'  res20: Boolean = true
scala> val thisIsBoring = !true  thisIsBoring: Boolean = false
scala> !thisIsBoring    res21: Boolean = true

The logical methods, logical-and (&&) and logical-or (||), take Boolean operands in infix notation and yield a Boolean result. For example:

  scala> val toBe = true
  toBe: Boolean = true
  
scala> val question = toBe || !toBe question: Boolean = true
scala> val paradox = toBe && !toBe paradox: Boolean = false

The logical-and and logical-or operations are short-circuited as in Java: expressions built from these operators are only evaluated as far as needed to determine the result. In other words, the right-hand side of logical-and and logical-or expressions won't be evaluated if the left-hand side determines the result. For example, if the left-hand side of a logical-and expression evaluates to false, the result of the expression will definitely be false, so the right-hand side is not evaluated. Likewise, if the left-hand side of a logical-or expression evaluates to true, the result of the expression will definitely be true, so the right-hand side is not evaluated. For example:

  scala> def salt() = { println("salt"); false }
  salt: ()Boolean
  
scala> def pepper() = { println("pepper"); true } pepper: ()Boolean
scala> pepper() && salt() pepper salt res22: Boolean = false
scala> salt() && pepper()  salt res23: Boolean = false
In the first expression, pepper and salt are invoked, but in the second, only salt is invoked. Given salt returns false, there's no need to call pepper.

Note

You may be wondering how short-circuiting can work given operators are just methods. Normally, all arguments are evaluated before entering a method, so how can a method avoid evaluating its second argument? The answer is that all Scala methods have a facility for delaying the evaluation of their arguments, or even declining to evaluate them at all. The facility is called by-name parameters and is discussed in Section 9.5.

5.6 Bitwise operations [link]

Scala enables you to perform operations on individual bits of integer types with several bitwise methods. The bitwise methods are: bitwise-and (&), bitwise-or (|), and bitwise-xor (^).[5] The unary bitwise complement operator (~, the method unary_~), inverts each bit in its operand. For example:

  scala> 1 & 2  
  res24: Int = 0
  
scala> 1 | 2   res25: Int = 3
scala> 1 ^ 3   res26: Int = 2
scala> ~1      res27: Int = -2
The first expression, 1 & 2, bitwise-ands each bit in 1 (0001) and 2 (0010), which yields 0 (0000). The second expression, 1 | 2, bitwise-ors each bit in the same operands, yielding 3 (0011). The third expression, 1 ^ 3, bitwise-xors each bit in 1 (0001) and 3 (0011), yielding 2 (0010). The final expression, ~1, inverts each bit in 1 (0001), yielding -2, which in binary looks like 11111111111111111111111111111110.

Scala integer types also offer three shift methods: shift left (<<), shift right (>>), and unsigned shift right (>>>). The shift methods, when used in infix operator notation, shift the integer value on the left of the operator by the amount specified by the integer value on the right. Shift left and unsigned shift right fill with zeroes as they shift. Shift right fills with the highest bit (the sign bit) of the left-hand value as it shifts. Here are some examples:

  scala> -1 >> 31 
  res28: Int = -1
  
scala> -1 >>> 31 res29: Int = 1
scala> 1 << 2    res30: Int = 4

-1 in binary is 11111111111111111111111111111111. In the first example, -1 >> 31, -1 is shifted to the right 31 bit positions. Since an Int consists of 32 bits, this operation effectively moves the leftmost bit over until it becomes the rightmost bit.[6] Since the >> method fills with ones as it shifts right, because the leftmost bit of -1 is 1, the result is identical to the original left operand, 32 one bits, or -1. In the second example, -1 >>> 31, the leftmost bit is again shifted right until it is in the rightmost position, but this time filling with zeroes along the way. Thus the result this time is binary 00000000000000000000000000000001, or 1. In the final example, 1 << 2, the left operand, 1, is shifted left two positions (filling in with zeroes), resulting in binary 00000000000000000000000000000100, or 4.

5.7 Object equality [link]

If you want to compare two objects for equality, you can use either ==, or its inverse !=. Here are a few simple examples:

  scala> 1 == 2 
  res31: Boolean = false
  
scala> 1 != 2  res32: Boolean = true
scala> 2 == 2  res33: Boolean = true
These operations actually apply to all objects, not just basic types. For example, you can use == to compare lists:
  scala> List(123) == List(123)
  res34: Boolean = true
  
scala> List(123) == List(456) res35: Boolean = false
Going further, you can compare two objects that have different types:
  scala> 1 == 1.0
  res36: Boolean = true
  
scala> List(123) == "hello" res37: Boolean = false
You can even compare against null, or against things that might be null. No exception will be thrown:
  scala> List(123) == null
  res38: Boolean = false
  
scala> null == List(123) res39: Boolean = false

As you see, == has been carefully crafted so that you get just the equality comparison you want in most cases. This is accomplished with a very simple rule: first check the left side for null, and if it is not null, call the equals method. Since equals is a method, the precise comparison you get depends on the type of the left-hand argument. Since there is an automatic null check, you do not have to do the check yourself.[7]

This kind of comparison will yield true on different objects, so long as their contents are the same and their equals method is written to be based on contents. For example, here is a comparison between two strings that happen to have the same five letters in them:

  scala> ("he"+"llo") == "hello"
  res40: Boolean = true

How Scala's == differs from Java's

In Java, you can use == to compare both primitive and reference types. On primitive types, Java's == compares value equality, as in Scala. On reference types, however, Java's == compares reference equality, which means the two variables point to the same object on the JVM's heap. Scala provides a facility for comparing reference equality, as well, under the name eq. However, eq and its opposite, ne, only apply to objects that directly map to Java objects. The full details about eq and ne are given in Sections 11.1 and 11.2. Also, see Chapter 28 on how to write a good equals method.

5.8 Operator precedence and associativity [link]

Operator precedence determines which parts of an expression are evaluated before the other parts. For example, the expression 2 + 2 * 7 evaluates to 16, not 28, because the * operator has a higher precedence than the + operator. Thus the multiplication part of the expression is evaluated before the addition part. You can of course use parentheses in expressions to clarify evaluation order or to override precedence. For example, if you really wanted the result of the expression above to be 28, you could write the expression like this:

  (2 + 2) * 7

Given that Scala doesn't have operators, per se, just a way to use methods in operator notation, you may be wondering how operator precedence works. Scala decides precedence based on the first character of the methods used in operator notation (there's one exception to this rule, which will be discussed below). If the method name starts with a *, for example, it will have a higher precedence than a method that starts with a +. Thus 2 + 2 * 7 will be evaluated as 2 + (2 * 7), and a +++ b *** c (in which a, b, and c are variables, and +++ and *** are methods) will be evaluated a +++ (b *** c), because the *** method has a higher precedence than the +++ method.

Table 5.3 here shows the precedence given to the first character of a method in decreasing order of precedence, with characters on the same line having the same precedence. The higher a character is in this table, the higher the precedence of methods that start with that character. Here's an example that illustrates the influence of precedence:

  scala> 2 << 2 + 2
  res41: Int = 32

The << method starts with the character <, which appears lower in Table 5.3 than the character +, which is the first and only character of the + method. Thus << will have lower precedence than +, and the expression will be evaluated by first invoking the + method, then the << method, as in 2 << (2 + 2). 2 + 2 is 4, by our math, and 2 << 4 yields 32. Here's another example:

  scala> 2 + 2 << 2
  res42: Int = 16

Since the first characters are the same as in the previous example, the methods will be invoked in the same order. First the + method will be invoked, then the << method. So 2 + 2 will again yield 4, and 4 << 2 is 16.

Table 5.3 - Operator precedence
(all other special characters)
* / %
+ -
:
= !
< >
&
^
|
(all letters)
(all assignment operators)

The one exception to the precedence rule, alluded to above, concerns assignment operators, which end in an equals character. If an operator ends in an equals character (=), and the operator is not one of the comparison operators <=, >=, ==, or !=, then the precedence of the operator is the same as that of simple assignment (=). That is, it is lower than the precedence of any other operator. For instance:

  x *= y + 1
means the same as:
  x *= (y + 1)
because *= is classified as an assignment operator whose precedence is lower than +, even though the operator's first character is *, which would suggest a precedence higher than +.

When multiple operators of the same precedence appear side by side in an expression, the associativity of the operators determines the way operators are grouped. The associativity of an operator in Scala is determined by its last character. As mentioned here of Chapter 3, any method that ends in a `:' character is invoked on its right operand, passing in the left operand. Methods that end in any other character are the other way around. They are invoked on their left operand, passing in the right operand. So a * b yields a.*(b), but a ::: b yields b.:::(a).

No matter what associativity an operator has, however, its operands are always evaluated left to right. So if b is an expression that is not just a simple reference to an immutable value, then a ::: b is more precisely treated as the following block:

val x = a; b.:::(x) }
In this block a is still evaluated before b, and then the result of this evaluation is passed as an operand to b's ::: method.

This associativity rule also plays a role when multiple operators of the same precedence appear side by side. If the methods end in `:', they are grouped right to left; otherwise, they are grouped left to right. For example, a ::: b ::: c is treated as a ::: (b ::: c). But a * b * c, by contrast, is treated as (a * b) * c.

Operator precedence is part of the Scala language. You needn't be afraid to use it. Nevertheless, it is good style to use parentheses to clarify what operators are operating upon what expressions. Perhaps the only precedence you can truly count on other programmers knowing without looking up is that multiplicative operators, *, /, and %, have a higher precedence than the additive ones + and -. Thus even if a + b << c yields the result you want without parentheses, the extra clarity you get by writing (a + b) << c may reduce the frequency with which your peers utter your name in operator notation, for example, by shouting in disgust, "bills !*&^%~ code!".[8]

5.9 Rich wrappers [link]

You can invoke many more methods on Scala's basic types than were described in the previous sections. A few examples are shown in Table 5.4. These methods are available via implicit conversions, a technique that will be described in detail in Chapter 21. All you need to know for now is that for each basic type described in this chapter, there is also a "rich wrapper" that provides several additional methods. To see all the available methods on the basic types, therefore, you should look at the API documentation on the rich wrapper for each basic type. Those classes are listed in Table 5.5.

Table 5.4 - Some rich operations
Code Result
0 max 5 5
0 min 5 0
-2.7 abs 2.7
-2.7 round -3L
1.5 isInfinity false
(1.0 / 0) isInfinity true
4 to 6 Range(4, 5, 6)
"bob" capitalize "Bob"
"robert" drop 2 "bert"

Table 5.5 - Rich wrapper classes
Basic type Rich wrapper
Byte scala.runtime.RichByte
Short scala.runtime.RichShort
Int scala.runtime.RichInt
Char scala.runtime.RichChar
String scala.runtime.RichString
Float scala.runtime.RichFloat
Double scala.runtime.RichDouble
Boolean scala.runtime.RichBoolean

5.10 Conclusion [link]

The main take-aways from this chapter are that operators in Scala are method calls, and that implicit conversions to rich variants exist for Scala's basic types that add even more useful methods. In the next chapter, we'll show you what it means to design objects in a functional style that gives new implementations of some of the operators that you have seen in this chapter.


Footnotes for Chapter 5:

[1] Packages, which were briefly described in Step 2 in Chapter 2, will be covered in depth in Chapter 13.

[2] figuratively speaking

[3] Overloaded methods have the same name but different argument types. More on method overloading in Section 6.11.

[4] All is not necessarily lost, however. There is an extremely slight chance your program with the *p might compile as C++.

[5] The bitwise-xor method performs an exclusive or on its operands. Identical bits yield a 0. Different bits yield a 1. Thus 0011 ^ 0101 yields 0110

[6] The leftmost bit in an integer type is the sign bit. If the leftmost bit is 1, the number is negative. If 0, the number is positive.

[7] The automatic check does not look at the right-hand side, but any reasonable equals method should return false if its argument is null.

[8] By now you should be able to figure out that given this code, the Scala compiler would invoke (bills.!*&^%~(code)).!().


Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use