In this chapter we will cover the core data and variable types in Scala. Let’s start with the definitions of the terms literal
, value
, variable
, and type
:
- A literal (or literal data) is data that appears directly in the source code, like the number
5
, the characterA
, and the text “Hello, World.
” - A value is an immutable, typed storage unit. A value can be assigned data when it is defined, but can never be reassigned.
- A variable is a mutable, typed storage unit. A variable can be assigned data when it is defined and can also be reassigned data at any time.
- A type is the kind of data you are working with, a definition or classification of data.
All data in Scala corresponds to a specific type, and all Scala types are defined as classes with methods that operate on the data.
The data stored in values and variables in Scala will get automatically deallocated by the Java Virtual Machine’s garbage collection when they are no longer used. There is no ability, or need, to deallocate them manually.
Let’s try exercising these terms by working with data in the Scala REPL. Scala values are defined with the syntax val <name>: <type> = <literal>
, so we will create a value with the name x
, type Int
(short for “integer
”), and assigned it the literal number 5
:
scala> val x: Int = 5
x: Int = 5
What happened here? The REPL (again, a Read-Evaluate-Print-Loop shell) read the value definition, evaluated it, and reprinted it as a confirmation. The new value, named x
, is now defined and available to use. So let’s use it:
scala> x
res3: Int = 5
scala> x * 2
res4: Int = 10
scala> x * 2
res5: Int = 10
scala> x / 5
res6: Int = 1
Each of these three input lines are valid Scala syntax and return an integer value. In each case, because a value is returned, the REPL repeats the value and its type and also assigns a unique, sequentially named value starting with res0
(short for “result”). You can choose to make use of these “result” values just like any value you explicitly define:
scala> res3 * res4
res7: Int = 50
Here the values res3
and res4
are multiplied, resulting in the value 50
being returned and stored in the new value named res7
.
Let’s try working with variables now. Variables, which unlike values are mutable and can be reassigned new values, are defined with the syntax var <name>: <type> = <literal>
.
scala> var a: Double = 2.72
a: Double = 2.72
scala> a = 355.0 / 133.0
a: Double = 2.669172932330827
scala> a = 5
a: Double = 5.0
In this example we defined the variable a to have the type Double
, a double-precision floating-point number. And then, because it is a variable, we reassigned it to a different value.
This has been a short introduction to using values, variables, types, and literals in Scala.In the rest of this chapter we will cover each of these subject areas in depth.
Values
Values are immutable, typed storage units, and by convention are the default method for storing data. You can define a new value using the val
keyword.
Syntax: Defining a Value
val <identifier>[: <type>] = <data>
Values require both a name and assigned data, but they do not require an explicit type. If the type is not specified (i.e., the “: <type>
” syntax is not included), the Scala compiler will infer the type based on the assigned data.
Here are some examples of defining values with their type in the Scala REPL:
scala> val x:Int = 20
x: Int = 20
scala> val greeting: String = "Hello, World"
greeting: String = Hello, World
scala> val atSymbol: Char = '@'
atSymbol: Char = @
You may have noticed from the syntax diagram that specifying the type in value definitions is optional. In situations where it is possible to deduce the type of the value based on its assignment (for example, the literal 20
in the first example is obviously an integer),you can leave off the type from a value definition. The Scala compiler will then discern the type of the value from its assignment, a process known as type inference. Values defined without a type are not typeless; they are assigned the proper type just as if the type had been included in the definition.
Let’s try the examples again without specifying their types:
scala> val x = 20
x: Int = 20
scala> val greeting = "Hello, World"
greeting: String = Hello, World
scala> val atSymbol = '@'
atSymbol: Char = @
In this example the values end up having the same types (Int
, String
, and Char
) as they did when the types were explicitly stated. The Scala compiler, via the REPL, was able to deduce that the literal 20
corresponds to the type Int
, the literal “Hello, World
” to the type String
, and the literal @
to the type Char
.
Using Scala’s type inference is a helpful shortcut when writing code because it removes the need to explicitly write the type of a value. As a guideline it should only be used when it does not reduce the readability of your code. In the case that someone reading your code would not be able to figure out what the type of the value is, it would be better to include the explicit type in the value definition.
Although type inference will deduce the correct type to use to store data, it will not override an explicit type that you set. If you define a value with a type that is incompatible with the initial value you will get a compilation error:
scala> val x: Int = "Hello"
<console>:10: error: type mismatch;
found : String("Hello")
required: Int
val x: Int = "Hello"
The error here affirms that an Int
type cannot be used to store a String
.
Variables
In computer science the term variable typically refers to a unique identifier corresponding to an allocated or reserved memory space, into which values can be stored and from which values can be retrieved. As long as the memory space is reserved, it can be assigned new values over and over again. Thus, the contents of the memory space are dynamic, or variable.
In most languages, such as C, Java, PHP, Python, and Ruby, this is the typical pattern for working with named, assignable memory storage. Variables are dynamic, mutable,and reassignable (with the exception of those defined with special restrictions such as Java’s final
keyword).
In Scala, values are preferred over variables by convention, due to the stability and predictability they bring to source code. When you define a value you can be assured that it will retain the same value regardless of any other code that may access it. Reading and debugging code is easier when a value assigned at the beginning of a code segment is unchanged through the end of the code segment. Finally, when working with data that may be available for the life span of an application, or accessible from concurrent or multithreaded code, an immutable value will be more stable and less prone to errors than mutable data that may be modified at unexpected times.
The example code and exercises in this book prefer the use of values over variables.However, in those places where variables are more suitable, such as local variables that store temporary data or accumulate values in loops, variables will certainly be used.
Now that the preference for values over variables has been explained in detail, we can put that aside and cover how to use variables in Scala.
The var
keyword is used to define a variable with a given name, type, and assignment.
Syntax: Defining a Variable
var <identifier>[: <type>] = <data>
Like values, variables can be defined with or without an explicit type. If no type is specified the Scala compiler will use type inference to determine the correct type to assign to your variable. Unlike values, variables can be reassigned new data at any time.
Here is an example of defining a variable and then reassigning it, in this case to the product of itself and another number:
scala> var x = 5
x: Int = 5
scala> x = x * 4
x: Int = 20
Although a variable can be reassigned, its designated type cannot, and so a variable cannot be reassigned data that has an incompatible type. For example, defining a variable of type Int
and then assigning it a String
value will result in a compiler error:
跟在命令行中可以重复定义变量是不一样的:
scala> var x = "Hello, world"
x: String = Hello, world
scala> var x = 5
x: Int = 5
只有在命令行中才可以这样做
cala> var x = 5
x: Int = 5
scala> x = "what's up?"
<console>:11: error: type mismatch;
found : String("what\'s up?")
required: Int
x = "what's up?"
However, defining a variable of type Double
and assigning it an Int
value will work because Int
numbers can be converted to Double
numbers automatically:
scala> var y = 1.5
y: Double = 1.5
scala> y = 42
y: Double = 42.0
Naming
Scala names can use letters, numbers, and a range of special operator characters. This makes it possible to use standard mathematical operators (e.g., *
and :+
) and constants (e.g., π
and φ
) in place of longer names to make the code more expressive.
The Scala Language Specification defines these operator characters as “all other characters in \u0020-007F and Unicode categories Sm [Symbol/Math] ... except parentheses (
[]`) and periods.” Square brackets (referred to in the text as parentheses) are reserved for use in type parameterization, while periods are reserved for access to the fields and methods of objects (instantiated types).
Here are the rules for combining letters, numbers, and characters into valid identifiers in Scala:
- A letter followed by zero or more letters and digits.
- A letter followed by zero or more letters and digits, then an underscore (
_
), and then one or more of either letters and digits or operator characters. - One or more operator characters.
- One or more of any character except a backquote, all enclosed in a pair of backquotes.
Names enclosed in backquotes(就是这个``符号) can, unlike the other names, be reserved keywords in Scala such as true
, while
, =
, and var
.
Let’s try out some of these naming rules in the REPL:
scala> val π = 3.14159
π: Double = 3.14159
The special character “π” is a valid Scala identifier.
scala> val $ = "USD currency symbol"
$: String = USD currency symbol
scala> val o_O = "Hmm"
o_O: String = Hmm
scala> val 50cent = "$0.50"
<console>:1: error: Invalid literal number
val 50cent = "$0.50"
^
The value name “50cent
” is invalid because names cannot start with numbers.In this case the compiler started parsing the name as a literal number and ran into problems at the letter “c
”.
scala> val a.b = 25
<console>:11: error: value b is not a member of Int
val a.b = 25
^ The value name “a.b” is invalid because a period isn’t an operator character.
scala> val `a.b` = 4
a.b: Int = 4
Rewriting this value with backquotes fixes the problem, although the aesthetics of using backquotes isn’t that great.
Value and variable names, by convention, should start with a lowercase letter and then capitalize additional words. This is popularly known as camel case, and though not required it is recommended for all Scala developers. This helps to distinguish them from types and classes which (also by convention, not by rule) follow camel case but start with an uppercase letter.
Types
Scala has both numeric (e.g., Int
and Double
) and nonnumeric types (e.g., String
) that can be used to define values and variables. These core types are the building blocks for all other types including objects and collections, and are themselves objects that have methods and operators that act on their data.
Unlike Java and C there is no concept of a primitive type in Scala. While the Java Virtual Machine supports the primitive integer type int
and the integer class Integer
, Scala only supports its own integer class, Int
.
Numeric Data Types
Table 2-1 displays Scala’s numeric data types.
Table 2-1. Core numeric types
Name | Description | Size | Min | Max |
---|---|---|---|---|
Byte | Signed integer | 1 byte | –127 | 128 |
Short | Signed integer | 2 bytes | –32768 | 32767 |
Int | Signed integer | 4 bytes | –2^31 | 2^31–1 |
Long | Signed integer | 8 bytes | –2^63 | 2^63–1 |
Float | Signed floating point | 4 bytes | n/a | n/a |
Double | Signed floating point | 8 bytes | n/a | n/a |
See the API documentation for java.lang.Float
and java.lang.Double
for a description of the calculated maximum and minimum values for these floating-point numbers.
Scala supports the ability to automatically convert numbers from one type to another based on the rank of the type. The numeric types in Table 2-1 are sorted by their automatic conversion rank, where the Byte type is the lowest and can be converted to any other type.
Let’s try this out by creating values of different types and automatically converting them to higher-ranked types:
scala> val b: Byte = 10
b: Byte = 10
scala> val s: Short = b
s: Short = 10
scala> val d: Double = s
d: Double = 10.0
The b
and s
values here were assigned to new values that had a higher rank, and so were automatically converted (or “upconverted” as some say) to the higher ranks.
Java developers will recognize the names of these types, which are wrappers around the core JVM types of the same names (except the JVM’s Integer
is Scala’s Int
). Wrapping JVM types ensures that Scala and Java are interopable, and that Scala can make use of every Java library.
Scala does not allow automatic conversion from higher ranked types to lower ranked types. This makes sense, because you could otherwise lose data if you convert to a type with less storage. Here is an example of trying to automatically convert a higher ranked type to a lower ranked type and the ensuing error:
scala> val l: Long = 20
l: Long = 20
scala> val i: Int = l
<console>:11: error: type mismatch;
found : Long
required: Int
val i: Int = l
^
You can choose to manually convert between types using the toType
methods available on all numeric types. Although this makes it possible to lose data by converting to a lesser ranked type, it is useful when you know that the data is compatible with the lower ranked type.
For example, here is a Long
value that can be safely converted to type Int
using the toInt
method, because its data is within the storage bounds of an Int
:
scala> val l: Long = 20
l: Long = 20
scala> val i: Int = l.toInt
i: Int = 20
我这时候就想:如果是超过了低精度类型的数呢:
scala> val l: Long = 999999999999999999l
l: Long = 999999999999999999
scala> val i: Int = l.toInt
i: Int = -1486618625
Scala并没有报错,是可以的
An alternative to using explicit types is to specify the type of your literal data directly,using Scala’s notation for literal types. See Table 2-2 for the full list of notations for specifying the types of literals.
Table 2-2. Numeric literals
Literal | Type | Description |
---|---|---|
5 | Int | Unadorned(adj. 朴素的;未装饰的) integer literals are Int by default |
0x0f | Int | The “0x” prefix denotes hexadecimal notation |
5l | Long | The “l ” suffix denotes a Long type |
5.0 | Double | Unadorned decimal literals are Double by default |
5f | Float | The “f ” suffix denotes a Float type |
5d | Double | The “d suffix denotes a Double type |
Literal Characters Are Case-Insensitive
You can use either lowercase or uppercase letters in Scala’s literal types.The literal number 5L
is the same as the literal number 5l
.
Let’s try out these literals by assigning them to new values without stating the type. The Scala REPL will use type inference to calculate the appropriate types for each value:
scala> val anInt = 5
anInt: Int = 5
scala> val yellowRgb = 0xffff00
yellowRgb: Int = 16776960
scala> val id = 100l
id: Long = 100
scala> val pi = 3.1416
pi: Double = 3.1416
Strings
The String
type represents “strings” of text, one of the most common core types in any programming language. Scala’s String
is built on Java’s String
and adds unique features like multiline literals and string interpolation(n. 插入;篡改;添写).
Write String
literals using double quotes, with special characters escaped with backslashes:
scala> val hello = "Hello There"
hello: String = Hello There
scala> val signature = "With Regards, \nYour friend"
signature: String =
With Regards,
Your friend
Like numeric types, the String
type supports the use of math operators. For example,use the equals operator (==
) to compare two String
values. Unlike Java, the equals operator (==
) checks for true
equality, not object reference equality:
scala> val greeting = "Hello, " + "World"
greeting: String = Hello, World
scala> val matched = (greeting == "Hello, World")
matched: Boolean = true
scala> val theme = "Na " * 16 + "Batman!" //what do you expect this to print
theme: String = Na Na Na Na Na Na Na Na Na Na Na Na Na Na Na Na Batman!
A multiline String
can be created using triple-quotes. Multiline strings are literal, and so do not recognize the use of backslashes as the start of special characters:
scala> val greeting = """She suggested reformatting the file
| by replacing tabs (\t) with newlines (\n);
| "Why do that?", he asked."""
greeting: String =
She suggested reformatting the file
by replacing tabs (\t) with newlines (\n);
"Why do that?", he asked.
String interpolation
Building a String
based on other values is reasonably easy to do with string addition.Here is a String
built by adding text before and after the Float
value:
scala> val approx = 355/113f
approx: Float = 3.141593
scala> println("Pi, using 355/113, is about " + approx + ".")
Pi, using 355/113, is about 3.141593.
A more direct way to combine your values or variables inside a String
is with string interpolation, a special mode where external value and variable names are recognized and resolved. The Scala notation for string interpolation is an “s
” prefix added before the first double quote of the string. Then dollar sign operators ($
) (with optional braces) can be used to note references to external data.(抄php的)
Here is the example again using string interpolation:
scala> println(s"Pi, using 355/113, is about $approx.")
Pi, using 355/113, is about 3.141593.
You will need the optional braces if you have any nonword characters in your reference (such as a calculation), or if your reference can’t be distinguished from the surrounding text:
scala> val item = "apple"
item: String = apple
scala> s"How do you like tem ${item}s"
res1: String = How do you like tem apples
scala> s"Fish n chips n vinegar,${"papper " * 3}salt"
res2: String = Fish n chips n vinegar,papper papper papper salt
An alternate format for string interpolation uses printf
notation, very useful when you want to control the data formatting such as the character count or display of decimal values. To use printf
notation change the prefix to an “f
” and follow the end of the reference immediately with the printf
notation:
If you are unfamiliar with printf
there are numerous online references for the format, including the official Javadoc for java.util.Formatter
, the underlying engine used by Scala to format these strings.
scala> val item = "apple"
item: String = apple
scala> f"I wrote a new $item%.3s today"
res4: String = I wrote a new app today
scala> f"Enjoying this $item ${355/113.0}%.5f times today"
res5: String = Enjoying this apple 3.14159 times today
These printf
notations make the references a little harder to read than in the previous examples, but do provide essential control over the output.
Now that we have learned how to control data output with strings, let’s find out how to do the opposite with regular expressions.
Regular expressions
A regular expression is a string of characters and punctuation that represents a search pattern. Popularized by Perl and command-line utilities like Grep, regular expressions are a standard feature in the libraries of most programming languages including Scala.
The format for Scala’s regular expressions is based on the Java class java.util.regex.Pattern
. I recommend reading the Javadoc (the Java API documentation) for java.util.regex.Pattern
if you are unfamiliar with this type, because Java’s (and thus Scala’s) regular expressions may be different from the format you have used with other languages and tools.
The String
type provides a number of built-in operations that support regular expressions. Table 2-3 displays a selection of these operations.
Table 2-3. Regular expression operations
Name | Example | Description |
---|---|---|
matches | “Froggy went a’ courting” matches “.* courting” | Returns true if the regular expression matches the entire string. |
Description | “milk, tea, muck” replaceAll (“m[^ ]+k”, “coffee”) | Replaces all matches with replacement text. |
replaceFirst | “milk, tea, muck” replaceFirst (“m[^ ]+k”, “coffee”) | Replaces the first match with replacement text. |
For more advanced handling of regular expressions, convert a string to a regular expression type by invoking its r
operator. This will return a Regex instance that can handle additional search and replace operations as well as capture group support. A capture group makes it possible to select items in a given string and convert them to local values based on the regular expression pattern. The pattern must include at least one capture group defined by parentheses, and the input must include at least one of the captured patterns to return the value.
Syntax: Capturing Values with Regular Expressions
val <Regex value>(<identifier>) = <input string>
Let’s try this out by capturing the numeric value from the output of the previous example (see “String interpolation” on page 18). We’ll use multiline strings to store our regular expression pattern, because they are literal and allow us to write a backslash without a second, escaping backslash:
scala> val input = "Enjoying this apple 3.14159 times today"
input: String = Enjoying this apple 3.14159 times today
scala> val pattern = """.* apple ([\d.]+) times .*""".r
The capture group is the series of digits and a period between the words apple
and times
.
pattern: scala.util.matching.Regex = .* apple ([\d.]+) times .*
The full regular expression type is scala.util.matching.Regex
, or just util.matching.Regex
.
scala> val pattern(amountText) = input
amountText: String = 3.14159
The format is admittedly a bit odd. The name of the new value containing the capture group match, amountText
, does not directly follow the val
identifier.
scala> val amount = amountText.toDouble
amount: Double = 3.14159
After converting the amount in text form to a Double
we have our numeric value.
Regular expressions serve as a compact and efficient means to process text, with operations such as matching, replacing, and capturing. If you are still new to regular expressions, it is worth investing time to study them because they are widely applicable in modern software development.
scala> val pattern(amountText) = input
scala.MatchError: tt (of class java.lang.String)
... 33 elided
如果没有匹配项就会报错
An Overview of Scala Types
In this section we will move on from numbers and strings to a broader look at the range of core types. All of Scala’s types, from numbers to strings to collections, exist as part of a type hierarchy. Every class that you define in Scala will also belong to this hierarchy automatically.
Figure 2-1 shows the hierarchy of Scala’s core (numeric and nonnumeric) types.
The open-headed arrows in the diagram indicate supertypes, a common notation in object-oriented diagrams. The multiple-arrow types at the bottom indicate that they are subtypes of every type in the system, including classes you define on your own.
In Table 2-4 you can see a full listing of the specific types mentioned in this diagram, followed by more complete descriptions.
Table 2-4. Core nonnumeric types
Name | Description | Instantiable |
---|---|---|
Any | The root of all types in Scala | No |
AnyVal | The root of all value types | No |
AnyRef | The root of all reference (nonvalue) types | No |
Nothing | The subclass of all types | No |
Null | The subclass of all AnyRef types signifying a null value | No |
Char | Unicode character | Yes |
Boolean | true or false | Yes |
String | A string of characters (i.e., text) | Yes |
Unit | Denotes the lack of a value | No |
The Any
, AnyVal
, and AnyRef
types are the root of Scala’s type hierarchy. Any
is the absolute root, and all other types descend from its two children, AnyVal
and AnyRef
.The types that extend AnyVal
are known as value types because they are the core values used to represent data. They include all of the numeric types we have covered plus Char
,Boolean
, and Unit
. AnyVal
types are accessed just like other types but may be allocated at runtime either on the heap as objects or locally on the stack as a JVM primitive value.All other types have AnyRef
as their root and are only ever allocated on the heap as objects. The term “Ref
“ in “AnyRef
” indicates they they are reference types that are accessed via a memory reference.
At the bottom of the Scala type hierarchy are the Nothing
and Null
types. Nothing
is a subtype of every other type and exists to provide a compatible return type for operations that significantly affect a program’s flow. For example, the return
keyword, which exits a function early with a return value, has a return type of Nothing
so it can be used in the middle of initializing a value and not affect the type of that value. Nothing
is only used as a type, because it cannot be instantiated.
The other bottom type is Null
, a subtype of all AnyRef
types that exists to provide a type for the keyword null
. A String
variable, for example, can be assigned null
at any time,such that the variable does not point to any string instance in memory. This assignment of null
to a variable declared as type String
is acceptable because null
is a compatible type for String
. Defining a type for null
is an example of how Scala’s syntax prefers the use of real types and instances to reserved keywords.
Char
is the only type that could also appear in “Numeric Data Types” on page 15. As the basis of the String
type it contains a single character and so is sometimes considered to be a unit of text. Essentially it is a scalar type that can be converted to and from other numbers.
Char
literals are written with single quotes, distinguishing them from String
literals,which are written with double quotes. If you’re familiar with the ASCII character numbering system, this example should be obvious:
scala> val c = 'A'
c: Char = A
scala> val i: Int = c
i: Int = 65
scala> val t: Char = 116
t: Char = t
The Boolean
type is limited to the values true
and false
. In addition to using true
and false
, you can also obtain Boolean
values from comparison and Boolean
logic operators:
scala> val isTrue = !true
isTrue: Boolean = false
scala> val isFalse = !true
isFalse: Boolean = false
scala> val unequal = (5 != 6)
unequal: Boolean = true
scala> val isLess = (5 < 6)
isLess: Boolean = true
scala> val unequalAndLess = unequal & isLess
unequalAndLess: Boolean = true
scala> val definitelyFalse = false && unequal
definitelyFalse: Boolean = false
What is the Difference Between & and && ?
The Boolean
comparison operators &&
and ||
are lazy in that they will not bother evaluating the second argument if the first argument is sufficient. The operators &
and |
will always check both arguments
Unlike many dynamic languages, Scala does not support automatic conversion of other types to Booleans
. A nonnull string cannot be evaluated as true
, and the number zero does not equal false
. If you need to evaluate a value’s state to a Boolean
, use an explicit comparison:
scala> val zero = 0
zero: Int = 0
scala> val isValid = zero > 0
isValid: Boolean = false
The Unit
type is unlike the other core types here (numeric and nonnumeric) in that instead of denoting a type of data it denotes the lack of data. In a way it is similar to the void
keyword used in Java and C, which is used to define a function that doesn’t return data. The Unit
type is similarly used in Scala as the return type for functions or expressions that don’t return anything. For example, the common println
function could be said to return a Unit
type because it returns nothing.
The Unit
literal is an empty pair of parentheses, ()
, which if you consider it is a fine representation of not having a value. If you want you can define a value or variable with the Unit
type, but again its common usage is for defining functions and expressions:
scala> val nada = ()
nada: Unit = ()
Now that we have covered the core types, let’s have a look at the operations they all have in common.
Type operations
Table 2-5 displays the operations available on all types in Scala. The toString
and hashCode
methods are required on all JVM instances.
Table 2-5. Common type operations
Name | Example | Description |
---|---|---|
asInstanceOf[ |
5.asInstanceOf[Long] | Converts the value to a value of the desired type. |
getClass | (7.0 / 5).getClass | Returns the type (i.e., the class) of a value. |
isInstanceOf | (5.0).isInstanceOf[Float] | Returns true if the value has the given type. |
hashCode | “A”.hashCode | Returns the hash code of the value, useful for hash-based collections. |
to |
20.toByte; 47.toFloat | Conversion functions to convert a value to a compatible value. |
toString | (3.0 / 4.0).toString | Renders the value to a String. |
Avoid asInstanceOf
The asInstanceOf
operation will cause an error if the value cannot be converted to the requested type. To avoid runtime errors with this operation, prefer the to<type>
typed conversion operations when possible.
The types we have covered so far in this chapter are all (with the possible exception of String
) scalar values, which represent a single element (or, of course with Unit
, the lack of any element). As a complement to these scalar values, we will finish the chapter with the Tuple
type, which can collect two or more of these values into a new,ordered element.
Tuples
A tuple is an ordered container of two or more values, all of which may have different types. You may be familiar with this term from working with relational databases, where a single row of a table is considered its own tuple. Tuples can be useful when you need to logically group values, representing them as a coherent unit. Unlike lists and arrays, however, there is no way to iterate through elements in a tuple. Its purpose is only as a container for more than one value.
You can create a tuple by writing your values separated by a comma and surrounded by a pair of parentheses.
Syntax: Create a Tuple
( <value 1>, <value 2>[, <value 3>...] )
For example, here is a tuple containing Int
, String
, and Boolean
values:
scala> val info = (5, "Korben", true)
ifno: (Int, String, Boolean) = (5,Korben,true)
You can access an individual element from a tuple by its 1-based index (e.g., where the first element is 1, second is 2, etc.):
scala> val name = info._2
name: String = Korben
An alternate form of creating a 2-sized tuple is with the relation operator (->
). This is a popular shortcut for representing key-value pairs in tuples:
scala> val red = "red" -> "oxff0000"
red: (String, String) = (red,oxff0000)
scala> val reversed = red._2 -> red._1
reversed: (String, String) = (oxff0000,red)
Tuples provide a generic means to structure data, and are useful when you need to group discrete elements for handling.
Summary
This may be a challenging chapter to see through to the end, because you had to read all about types and data without learning how to do real programming in Scala yet. I’m glad you did.
What was the oddest or most-unexpected part of this chapter? The use of keywords to announce value and variable definition? The reversed manner (if you’re coming from Java) of defining a variable’s name before its type? The idea that much of your code can use fixed, nonreassignable values instead of (variable) variables?
If these ideas were hard to take, the good news is that, as you gain experience in Scala developemnt, they will become quite normal. Eventually they may even seem to be obvious choices for a well-designed functional programming language.
At this point you should know how to define your own values and variables, although we haven’t yet learned where to come up with useful data to store in them. In the next chapter you will study ways to derive and calculate this data using logical structures known as expressions.