© Jason Lee Hodges 2019
J. L. HodgesSoftware Engineering from Scratchhttps://doi.org/10.1007/978-1-4842-5206-2_5

5. Basic Data Types

Jason Lee Hodges1 
(1)
Draper, UT, USA
 

It is often an impulsive tendency among programming students to dismiss data types as a less than impactful topic. After all, Scala is inferring the type for each expression or variable for you, and since there are so many other topics to learn, why spend the upfront time on a seemingly minor topic, right? Not only that, but in many introductory computer science languages, the type system is neglected entirely. Why, then, is it important to understand the difference between data types?

Like a benevolent creator, what you code defines the rules of the world in which your program operates. Should your software allow for the representation of a half human or a negative human? Or would it make more sense for the representation of a population’s census to be calculated with positive integers only? Will your program have a form of population control by defining a max number of humans that can be represented within the software? Can they have text-based names or will they all be represented as numbers like some post-apocalyptic dystopian society? How might families be defined? If your software is in charge of distributing medication to these families, at what level of precision should the amount of medication be rounded? As a creator, having control over such decisions becomes an incredible responsibility, especially as the impact of your software grows.

In the previous chapter, you were shown an example of a semantic error. These kinds of errors occur when the computer interprets an expression in a manner that you, the programmer and creator, did not anticipate. The reason the result of the expression is incorrect is that you allowed Scala to interpret the type of the expression as needing to be an integer. If this error occurred when calculating medication dosages for a family, the impact could be incredibly costly. This is the exact reason why types are so important. If you know and understand types, you can avoid potentially catastrophic semantic errors in your code. Scala, unlike many introductory programming languages, allows you to define the types of your variables and expressions upfront, if you desire, instead of simply asking it to infer the types for you. It is this method of control that allows you the power to create exceptionally precise and optimally performant software.

In this chapter, you will be introduced to the basic data types of the Scala language that are necessary to garner such control. You will also learn some of the properties and behaviors of each data type as well as how to assign these types to variables. We will start with the numeric types and how they all relate within the Scala type system. After that you will be briefly introduced to Boolean types, followed by groups of data. Finally, we will finish off with text-based data and string manipulations.

Numeric Types

There are several different types of numeric data in computer science. You might wonder why all numbers aren’t represented the same way, or why there needs to be a delineation between integers vs. rational numbers. Besides simply determining precise methods of control, the computer needs to know how much memory it needs to reserve for the given type of data. A very large number is going to take up more memory than a small number, and an integer will take up different amounts of memory than a rational number. Dynamically typed languages (which are languages that don’t allow you to define a type for your variables) tend to either be conservative and reserve more memory than is necessary or do type conversions on the fly, when necessary, in order to account for needing more memory than was initially allocated. And while that is convenient, it also costs an enormous performance penalty on your program and relegates control away from you and gives it to the interpreter. The beauty of Scala is that it gives you the choice as to whether you want to let the language infer the type, whether you want to define the type, or if you want to choose among some varying “in-between” options.

One such “in-between” option is known as the AnyVal type. AnyVal is a super-type of all the primitive data types, meaning that when you specify a type for your mathematical expression or variable, any number type that you define is also considered an AnyVal type. This is known as type class inheritance. A real-world example of this is the fact that all squares are rectangles but not all rectangles are squares. A rectangle would be the super-type in this example, and the square would inherit the type of rectangle while also being more specific about its own properties (thereby being a sub-type). Listing 5-1 provides an example of how you might assign the type AnyVal to a variable.
scala> var x: AnyVal = 5
x: AnyVal = 5
scala> var y: AnyVal = 6.0
y: AnyVal = 6.0
Listing 5-1

Assigning the AnyVal type to a variable

As you can see from this example, the way to assign a type to a variable is by typing a colon after the variable name, typing out the name of the type, and then following that up with the normal assignment operation. You’ll notice that the type of the evaluated expression is AnyVal. If you had not explicitly assigned a type, Scala would have inferred the type of the first expression as an Integer and the second expression as a Double. But the AnyVal type can accept both types of numbers, therefore acting as a kind of “in-between” or general type. The problem with defining general types is, just like a rectangle has less specific properties than a square, Scala knows less about what you are trying to do with a general type variable and therefore allows less operations. Scala knows that the AnyVal might be some type of number and that it should not accept any non-primitive data types (like lists and objects which we will get to later) for this particular variable. But because you have not been specific about what kind of number the variable is, how should it handle division? It will not know whether to handle integer division or what to do with any potential remainders. It will also not know how much memory it should allocate as the result of an expression that adds two AnyVals. The same is also true for other mathematical operations. So, if you are going to do any mathematical operations with the AnyVal type, you will end up needing to convert the variable to a more specific data type. Table 5-1 lists all the specific numeric types that AnyVal can be converted to and their properties.
Table 5-1

A list of numeric sub-types of the AnyVal super-type

Type Name

Description

Short

A 16-bit whole number with value ranging from –32768 to 32767.

Int

A 32-bit whole number with value ranging from –2147483648 to 2147483647.

Long

A 64-bit whole number with value ranging from –9223372036854775808 to 9223372036854775807.

Float

A 32-bit rational number.

Double

A 64-bit rational number.

As you can see, the main difference between the numeric types is how big of a value they can hold, which subsequently determines how much memory the computer should allocate for a variable of that type. If you know that the variable will only store a number within a very small range, it would be optimal for you to define that variable with the corresponding type that fits that range. This is incredibly important when programming for embedded systems or small devices that have very limited memory and need to be heavily optimized for performance. That being said, most computers have a significant amount of memory these days, so sticking to Integers and Doubles usually works out just fine if you’re not sure what range your number will fall into. To further illustrate the difference between the sizes of data that your numbers can fall into, Listing 5-2 demonstrates the same expression evaluated as a Float and also as a Double.
scala> var x: Float = 2f/3f
x: Float = 0.6666667
scala> var y: Double = 2.0/3.0
y: Double = 0.6666666666666666
Listing 5-2

An expression evaluated and stored with different data types

There are two things to notice about the differences between these expressions. The first is the type casting that occurs which allows Scala to understand the literal values in which you are trying to divide. Just like adding a decimal at the end of an integer tells Scala that the type of the numeric literal you are evaluating is a Double, adding an f to the end of a number tells it you intend the type of the literal value to be a Float. You could have stored these values as variables first in order to explicitly set the type, but this is an example of how you would set the type for numbers using an inline expression. The corresponding type casting characters for Double and Long are D and L, respectively, although the D is often omitted in favor of setting the type with a decimal.

The second thing to notice is the number of decimal places that this infinite fraction is allowed to store and what occurs at the last digit in terms of rounding. The Double type stores double the amount of decimal places (hence the name). Alternatively, because it can only store half the number of decimal places, the Float type rounds at a much lower level of precision. This type of rounding behavior could have a big impact on your code if you are dealing with use cases that have extreme fractional sensitivity (like our example of the medication dosages) so you will need be aware of this upfront.

Note

There is not an inline shorthand expression for setting a value as a Short data type. However, you can attempt to convert any data type into another data type by using the .asInstanceOf function. Functions as a whole will be covered later in the book, but for now you can try casting an Integer to a Short using the code 1.asInstanceOf[Short].

If you are still confused as to which numeric data type you should use at any given time, don’t worry. As a rule of thumb, if you know your number will always be a whole number, use an Integer; otherwise, use a Double.

Booleans

In addition to the five numeric data types, there is another important type that is a sub-type of AnyVal that was not listed in Table 5-1. You have seen this data type before in the section that described comparison operators, however. That data type is the Boolean. A Boolean value is simply a way to store the notion of “on” or “off”, 0 or 1, true or false (just like the examples of binary in the first chapter). Some languages store Boolean values in the binary 0 or 1 format, but Scala uses the true/false format. Listing 5-3 shows an example of explicitly setting a variable to a Boolean value and also how expressions can be evaluated to a Boolean value using comparison operators.
scala> var z: Boolean = true
z: Boolean = true
scala> var f: Boolean = 2 == 3
f: Boolean = false
scala> !true
res1: Boolean = false
scala> (!false || 3 != 3) && ((5+3) == 8 && true)
res2: Boolean = true
Listing 5-3

Examples of a Boolean assignment and a Boolean expression

Notice the final expression uses the parentheses operator to group nested comparisons. Take a moment to understand how the expression evaluated to true since understanding Boolean expressions of this type will become extremely important in the next chapter when evaluating control flow. Within the first group of parentheses, the left half of the Or operation uses a Not operator (the exclamation point) which turns the false value into true. Even though the second operator of “3 does not equal 3” is false, because the expression is an Or, only one of the two sides needs to be true for the overall expression to be true. So, you can simplify the first grouped expression to true.
scala> true && ((5+3) == 8 && true)
res3: Boolean = true
scala> true && (true && true)
res4: Boolean = true
scala> true && true
res5: Boolean = true
Listing 5-4

Simplifying a nested expression

Listing 5-4 continues to simplify the expression progressively to show you how you might further break down this nested expression of comparisons and Booleans. Any And comparison operator will need both sides to evaluate to true in order for the overall expression to be true. As you can see, as the expression continues to be simplified, every expression ultimately equates to true. Make sure you fully understand how to simplify and evaluate Boolean expressions before moving on.

Exercise 5-1

Evaluate the following Boolean expressions without using the Scala REPL:
  1. 1)

    !((18 - 3 == 15) && 7 / 2 != 3) || (false && (3+1) != 4)

     
  2. 2)

    !(1 > 3 && (false && 5 <= 25 / 5))

     
  3. 3)

    (Math.abs(Math.pow(4,-2)) == 16 && true)

     

Groups of Data

Now that you’ve had a general overview of a few different basic data types, the next concept to understand is how to group pieces of data together. This could be either literal data values themselves or their stored variable names. The main thing to grasp about these groups of data is that instead of the computer allocating one slot of memory for a single data value, it is allocating several locations in memory and linking them together in a fashion such that you can refer to them as a single entity. A nice analogy would be to think of the basic data types you have learned so far as individuals and a group of data as a family made up of individuals. The two main groups of data that you need to be aware of initially are called lists and maps.

Lists

Lists have various forms and implementations depending on the language that you are using. You may hear several languages referring to a group of data known as Arrays which, for the purpose of learning this data type, you can think of as the same thing as a List (we will dive more into implementation details of this when we cover data structures later on in the book). Simply put, Lists or Arrays are just a collection of data items.

As an example of such a collection, we could think of directions from one location to another location as a group of data. Each data point would contain the distance that needs to be traveled and then whether you need to turn left or right. To put this in terms of data types that have been demonstrated thus far in this book only, let’s use a Boolean value to determine which direction to turn. True will mean that you need to turn right, and false will mean that you do not need to turn right (and therefor you should turn left). Listing 5-5 illustrates this example with a Scala List.
scala> var right = true
right: Boolean = true
scala> var left = false
left: Boolean = false
scala> var directions = List(5,right,6,left,3,left)
directions: List[AnyVal] = List(5, true, 6, false, 3, false)
Listing 5-5

The instantiation of a Scala list

This example demonstrates that the appropriate syntax to create a list in Scala is to use the List keyword, followed by parentheses that contain a comma-separated list of data. Note that the inferred type of the directions variable is a List[AnyVal]. What that means is that the variable type is a List that can contain any data type that is a sub-type of the AnyVal type, which makes sense since all of the data types that we have learned so far (and subsequently used in our list) have the AnyVal type as their super-type. If you were to instantiate a variable like var x: List[Int], then the values you put into that list must all be integers or Scala will throw an error.

The way data in a list is organized is by an ordered index, meaning that each position within the list has a corresponding value that marks its place, much like a street address for a house. If you want to extract just one value from the list, you would provide its index value to a set of parentheses following the list’s variable name. The indexes start at 0 and increment up for each item added to the list. A representation of the indexes for our example list is illustrated in Table 5-2.
Table 5-2

An illustration of the auto-incrementing indexposition of a List

Index

0

1

2

3

4

5

Value

5

true

6

false

3

false

If you want to add a new item to this list, you would type the list variable name, followed by the :+ operator, and then the item you wish to add. It’s worthy to note that the item you wish to add could be yet another list, as groups of data can be nested. You can also combine two lists, if you wish, using the ++ operator. This is known as list concatenation. Examples of these List operators are shown in Listing 5-6.
scala> var first = directions(0)
first: AnyVal = 5
scala> var last = directions(5)
last: AnyVal = false
scala> directions :+ 6
res0: List[AnyVal] = List(5, true, 6, false, 3, false, 6)
scala> directions :+ List(1,2,3)
res1: List[Any] = List(5, true, 6, false, 3, false, List(1, 2, 3))
scala> directions ++ List(1,2,3)
res2: List[AnyVal] = List(5, true, 6, false, 3, false, 1, 2, 3)
Listing 5-6

Examples of various operators that apply to Lists

Notice that the type of res1 is a List[Any] rather than a List[AnyVal]. Why might that be? Well, the data type List is not a sub-type of the AnyVal type even though it contains only AnyVal types. Groups of data are sub-types of the AnyRef data type. And both AnyVal and AnyRef data types are sub-types of the Any type (which all types ultimately roll up to). So, because res1 is assigned to a List that contains both AnyVal types and a List, which is an AnyRef type, the variable must be assigned to the most generic ancestor that all items in the list share, which in this case is the Any type.

You might also notice that these operators do not directly affect the list, but rather evaluate like an expression that returns a result. The expression that evaluates to res2 is not impacted by the expression that evaluates to res1 because the directions list has not been altered by these operators. Operations like these that don’t affect the value stored in the underlying variable are said to be immutable operations. If you wanted to have these immutable operations build on one another, you would need to store the result of each expression in a new variable and then perform the next operation on the new variable. You would then continue this pattern of storing each new expression result in a new variable and performing the next operation on each new variable.

Besides these expression operators, there are a few extra pieces of useful syntax that are commonly used on Lists that you should be aware of. The first is known as a slice which can return to you a piece of the list. It takes a starting index (inclusive) and an ending index (which is not included) and returns a List with all the values in between. The second is called length that returns to you the integer value of the number of items in the list. If you wanted to take a slice halfway through the list until the end of the list, you could do so by first knowing the length of the list. Finally, there is contains which will return a Boolean value that represents whether or not the value you provided exists in the list. You can observe these in action in Listing 5-7.
scala> directions.slice(1,3)
res3: List[AnyVal] = List(true, 6)
scala> directions.length
res4: Int = 6
scala> directions.slice(directions.length/2, directions.length)
res5: List[AnyVal] = List(false, 3, false)
scala> directions.contains(9)
res6: Boolean = false
Listing 5-7

Additional List methods

There are several other methods and operators that apply to Lists and list-like data structures, but this will give you a solid start. The important piece here is understanding that grouping data in order by an index position is a common data type that you will use extensively in software engineering.

Maps

Maps are a very similar data type to Lists, but instead of the data within the group being organized by index position, data in a map is organized by an unordered key/value pair. You as the developer get to define the key used to access the data you are looking for instead of relying on knowing its index value. An analogous concept would be looking up a word in a dictionary to find its definition. The word in this scenario is the key and the definition is the value. In fact, the map data type in Python is known as a dictionary for this reason.

The syntax to instantiate a new map is to first use the Map keyword, followed by parentheses that contain a comma-separated list of key/value pairs. The key/value pairs are separated by the -> operator. Keys can be any literal value that you have learned so far and also text values, denoted with double quotation marks as you will see in the next section. It is worthy to note that, just like you wouldn’t see the same word listed twice in the dictionary, there can be no duplicate keys in a map. An example of creating a map and accessing its values using a key is presented in Listing 5-8.
scala> var numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)
scala> numbers("one")
res7: Int = 1
scala> numbers("two")
res8: Int = 2
Listing 5-8

Creating a Map of key/value pairs

Notice that, similar to a list, the resulting Map type takes in data types in brackets that define what an acceptable key type might be and what an acceptable value type might be. In this example the resulting map type is Map[String,Int], meaning all the keys must be of type String (text data) and all the values must be of type Int.

Just like lists, the map data type can be nested with lists or even other maps. To add a new key/value pair to a map, you use the + operator followed by parentheses containing a comma-separated list of pairs to add. To concatenate two maps together, you can use the ++ operator. Again, just like lists, these operations are immutable, meaning they do not affect the source data but instead evaluate to a result that contains new Maps that can be stored however you like. Examples of these immutable operators can be seen in Listing 5-9.
scala> numbers = numbers + ("three" -> 3, "four" -> 4)
numbers: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2, three -> 3, four -> 4)
scala> var moreNumbers = Map("five" -> 5, "six" -> 6)
moreNumbers: scala.collection.immutable.Map[String,Int] = Map(five -> 5, six -> 6)
scala> numbers = numbers ++ moreNumbers
numbers: scala.collection.immutable.Map[String,Int] = Map(four -> 4, three -> 3, two -> 2, six -> 6, five -> 5, one -> 1)
scala> var nested = numbers + ("nest" -> List(1,2,3))
nested: scala.collection.immutable.Map[String,Any] = Map(four -> 4, nest -> List(1, 2, 3), three -> 3, two -> 2, six -> 6, five -> 5, one -> 1)
Listing 5-9

Immutable map operations

As you can see, the first expression adds the “three” and “four” keys to the numbers map and then reassigns the original variable numbers to the result of the expression. This is only possible because numbers was instantiated as a var variable type, meaning it is mutable and can be changed. Otherwise, the result of the expression would need to be stored in a new variable. The second expression creates a new map called moreNumbers so that the third expression can demonstrate the concatenation of two Maps together, again reassigning the numbers variable to the result of the expression. You’ll notice that the result of the concatenation does not appear to be in order. As stated earlier, Maps contain unordered key/value pairs. So when expressions are evaluated, there is no guarantee that the result will resemble the original order. The reasons why this is the case will be covered more later when covering data structures, but for now just know that you do not need to care about the order since you access the variables from their key directly and not their ordered position within the Map. Finally, the fourth expression demonstrates how a Map can take a list as a value. Notice the resulting map type is a Map[String,Any] rather than a Map[String,Int] since a list is not a integer.

In addition to adding new key/value pairs and concatenation, Maps have a few other methods that can be called that will be useful for you to know. The first is the keys method that returns a list of all the keys in the Map. The second is the values method that returns a list of all the values in the Map. The third is a size method which, similar to the length method of a list, returns an integer representing the number of items in the Map. And finally there is the contains function in which you provide a key to the parentheses of the method, and it will return whether or not that key exists within the Map. Examples of these methods are exhibited in Listing 5-10.
scala> numbers.keys
res9: Iterable[String] = Set(four, three, two, six, five, one)
scala> numbers.values
res10: Iterable[Int] = MapLike.DefaultValuesIterable(4, 3, 2, 6, 5, 1)
scala> numbers.size
res11: Int = 6
scala> numbers.contains("seven")
res12: Boolean = false
Listing 5-10

Additional Map methods

Just like Lists, Maps have several other useful methods that you will use later on, but the important concept to grasp here is that a Map is a collection of unordered key/value pairs that can be accessed based on the key.

Exercise 5-2

Create a Map that contains other maps as the values to your keys. See if you can figure out how to assign the value of a nested key within your Map of Maps to a variable.

Characters and Strings

You briefly saw a String data type in the last section. Strings are text-based data represented by a series of alphabetic, numeric, or special characters encased within double quotation marks. That should seem somewhat intuitive to you. But perhaps less obvious is the fact that a String data type is actually just a collection or group of individual characters. Thus, strings mimic a lot of the same properties of the group data types that you just saw. Both groups and strings are sub-types of the AnyRef data type. But, you might ask, if a String type is a collection of characters, then what is a character?

Characters are individual letters, numbers, or special characters that are encased in single quotes and are represented by the Char data type. They are different from groups of letters in that they can only occupy a single byte of the computer’s memory. Because Char is a primitive data type just like all of the numeric data types and the Boolean data type, it is a sub-type of the AnyVal type. Any data type that is a group of data must be a sub-type of the AnyRef type. Therefor, if you tried to assign a Char variable two letters or if you tried to assign it with double quotes instead of single quotes, Scala would throw an error. The only way to represent text that is more than one character is through the String data type which, aptly named, strings together individual characters. Listing 5-11 provides an example of assigning both a String and a Char data type.
scala> var c: Char = 'a'
c: Char = a
scala> var s: String = "Text data"
s: String = Text data
scala> '1' == 1
res13: Boolean = false
scala> "1" == 1
res14: Boolean = false
scala> "1" == '1'
res15: Boolean = false
Listing 5-11

Assigning Characters and Strings

It is important to note that the literal character '1' and the literal integer 1 are not equal. The first is a text-based character that has the properties and methods that all text-based characters have. The second is an actual number that can be used in arithmetic calculations. The same is also true for the literal string "1"; it is not equal to the integer 1 or the character '1'. To further demonstrate their difference, imagine subtracting "1" from "a". What might the result be? As you might have guessed, that would throw an error as it would have no semantic meaning. Contrast that with subtracting two integers, which intuitively makes sense. Perhaps unexpectedly though, Scala does allow you to add Strings together. That operation is similar to joining two groups of data as you have seen before. Listing 5-12 illustrates such an attempt.
scala> "1" + "a"
res16: String = 1a
scala> "5" + 5
res17: String = 55
Listing 5-12

String and Character operators

As you can see, adding two Strings just concatenates the strings together. Adding a String to an Int, however, might yield unexpected results. A cursory observer might suspect that the expression "5" + 5 should evaluate to 10. However, because one of the data types is a String, Scala coerces the other data type to a String and concatenates them together, since a String cannot be used in actual arithmetic. This is why understanding data types is so important. Imagine that a string-based representation of a number sneaks its way into your program through an inadvertent bug and you evaluate a dosage of medication (to use our previous example) to be 55 instead of 10. That would be potentially catastrophic. If you do find a string representation of data in your code that you want to evaluate to a number, you would simply call the toInt method on it to cast it to an integer. So, in this example, the corrected expression of "5".toInt + 5 would evaluate to the expected result of 10.

String Manipulation

You’ve already seen an example of concatenating two strings together. But, just like other groups of data, strings have several other useful methods that you will want to know for everyday coding. These methods will become extremely important when you start to work with data that comes from other programs or sources that you don’t have control over as the data will likely not be in the format that you need it. These methods will allow you to cleanse it prior to any necessary processing.

The first set of methods that you’ll need to know involves the case of the string. The toUpperCase method converts all the characters in the string to uppercase, and the toLowerCase method converts all the characters in the string to lowercase. These are most often used when comparing two strings to ensure that the comparison is not case sensitive. That being said, there is also an equalsIgnoreCase method that accomplishes this same goal. Examples of these methods are shown in Listing 5-13.
scala> var VaderQuote = "No, I am your Father."
VaderQuote: String = No, I am your Father.
scala> VaderQuote.toUpperCase
res18: String = NO, I AM YOUR FATHER.
scala> VaderQuote.toLowerCase
res19: String = no, i am your father.
scala> VaderQuote == VaderQuote.toLowerCase
res20: Boolean = false
scala> VaderQuote.equalsIgnoreCase("nO, I Am yOuR fAtHer.")
res21: Boolean = true
Listing 5-13

String methods involving case

Besides case sensitivity, another scenario that might come up when using the equals comparison operator with strings is the existence of extra whitespace or extra margin characters like quotes, parentheses, or brackets in a string. These extra unwanted characters will result in an equals comparison operator evaluating to false. Extra whitespace can be removed by calling the trim method on a string, and extra margin characters can be removed by calling stripPrefix or stripSuffix on a string and provide the character that you need to remove from the start and the end. Examples of these methods are shown in Listing 5-14.
scala> " hello world ".trim == "hello world"
res22: Boolean = true
scala> "[hello world]"
.stripPrefix("[").stripSuffix("]")
res23: String = hello world
Listing 5-14

Examples of trim, stripPrefix, and stripSuffix methods on strings

Another strategy that you could use to remove characters from a string is to use the replace method. The replace method takes two arguments. The first argument is used to scan the string and find matches for sub-strings that you want to replace. The second argument is what you want to replace the sub-string with. So, in the example in Listing 5-14 instead of stripping the prefix and suffix, we could have called .replace("[", "") which would replace the bracket with an empty string and accomplish the same task. Listing 5-15 shows a few more examples of this functionality.
scala> "Hello John".replace("John", "Jane")
res24:String = Hello Jane
scala> "I like to eat apples and bananas".replace("a", "o")
res25: String = I like to eot opples ond bononos
Listing 5-15

Examples of the replace method

The next method you will need to use quite often is called a template string. A template string is an easy way in Scala to accomplish the task of string interpolation, which is a process wherein the language scans strings for variables or expressions and replaces them with their evaluated form. Almost every programming language has some form of string interpolation, but they have varying implementations.

The syntax to use a template string is to simply put an s in front of the quotes of the string that you want to use. Then within the quote you can add a $ in front of a variable name or you can use curly brackets along with the $ as an alternative if you need to evaluate an expression, like so: ${ x }. If you need to use a quotation mark or other special characters within your string, you can use triple quotes to ensure that Scala doesn’t interpret the quote inside your string as the end of the string. You can observe these methods in action in Listing 5-16.
scala> """Vader said, "No, I am your father.""""
res26: String = Vader said, "No, I am your father."
scala> var number = "four"
number: String = four
scala> var numbers = Map("one" -> 1, "two" -> 2, "three" -> 3, "four" -> 4)
 numbers: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2, three -> 3, four -> 4)
scala> s"""The numeric representation of "$number" is ${ numbers(number) }."""
res27: String = The numeric representation of "four" is 4.
Listing 5-16

String interpolation

You can see how string interpolation might be useful if you needed to create this same string for each of the key/value pairs in this numbers map. You would not need to rewrite the whole string every time, rather you could just replace the value of the number variable and then re-evaluate the template string expression. Try evaluating this same expression for each key/value pair to cement this concept in your mind.

Note

It is really easy to reuse a previous line of code from the terminal or the REPL. In both cases you can simply push the up arrow continuously on your keyboard and it will cycle through previously executed commands. This might be useful in this string interpolation example so that you don’t have to re-type the expression each time you change the value of the number variable.

Another common task you may need to accomplish with string manipulation is extracting just a piece of a string. Just as you can slice a list, you can also slice a string. Just like a list, each character of a string has an index position starting with 0. You can also use the indexOf method to find the index position of the start of a sub-string if you don’t know the index that you need to pass to the slice function. Listing 5-17 demonstrates this functionality.
scala> var quote = "Great Scott!"
quote: String Great Scott!
scala> quote.slice(0,3)
res28: String = Gre
scala> quote.slice(quote.indexOf("Scott"), quote.length)
res29: String = Scott!
Listing 5-17

String slicing using the indexOf method

Take a close look at the last expression. You’ll notice that strings also have the length method, just like lists, which tells you how many characters there are in the string. As usual, if you don’t understand the expression, you can pull sub-expressions out of it to evaluate in the REPL and simplify it in your mind. It is useful to note that if Scala cannot find the index of a sub-string in your string, then it will return an index of −1, meaning not found. This could be useful in combination with a comparison operator if you are simply trying to see if a sub-string exists within a given string. Another method you could use to solve the same use case is called contains. Contains simplifies the need to write out a comparison operator to get the Boolean value of the expression. Listing 5-18 represents both options.
scala> quote.indexOf("Scott") >= 0
res30: Boolean = true
scala> quote.indexOf("Marty") >= 0
res31: Boolean = false
scala> quote.contains("Scott")
res32: Boolean = true
scala> quote.contains("Marty")
res33: Boolean = false
Listing 5-18

Checking for the existence of a sub-string in a string

The final two methods that are going to be extremely useful are the split and mkString methods (which are opposites of each other). Their use will be markedly demonstrable when dealing with data from other sources, since often the data is presented to you in a continuous string format that you need to be able to parse through in order to use. Once you have processed it according to your needs, you may often need to return it back to the source in a single continuous string format just as you received it, so you will need to put it back. The split method allows you to split a single string of data into a collection of data that has been separated by a certain character (most often this is a comma in the case of csv files, but it is not uncommon to see a tab or a pipe character either). A character that separates data in this way is called a delimiter, and data that contains a delimiter is called delimited data. The mkString method can be performed on a collection of data to put it back into a continuous string delimited with whatever character you like. Listing 5-19 demonstrates how you might take a comma-delimited string and turn it into a pipe delimited string using these two methods.
scala> var csv = "Married,Male,28,6ft,170 lbs."
csv: String = Married,Male,28,6ft,170 lbs.
scala> var parsed = csv.split(",")
parsed: Array[String] = Array(Married, Male, 28, 6ft, 170 lbs.)
scala> var psv = parsed.mkString("|")
psv: String = Married|Male|28|6ft|170 lbs.
Listing 5-19

Converting comma delimited data intopipe delimited data

As you can see, there are many useful methods for manipulating strings in Scala. You might not fully understand the applications for all of these methods right now, but they will become more apparent in future examples once more functionality of the Scala language has been exposed to you. For now, just ensure that you have memorized each function. When you are introduced to future examples and exercises, having to look up the syntax for these simple functions will start to slow you down when you could be focusing on the new concept instead. If you need to, write these methods down on flash cards and drill them with a peer.

Summary

In this chapter, you were introduced to the basic types of data and how they relate within the Scala type system. A diagram of all of their relationships from the official Scala documentation is presented in Figure 5-1 for reference. We also discussed why controlling data types in your program is important, even for introductory programming students. As you become more experienced, you will learn when to allow Scala to infer your types for you as it is a convenience that allows for greater productivity. While you are learning, though, it is important to intentionally control your data types. In this chapter, you were also shown some key methods that each data type implements and some examples on how and when to use them. Finally, you were given a large list of methods that string types can use for manipulating text-based data.
../images/476847_1_En_5_Chapter/476847_1_En_5_Fig1_HTML.png
Figure 5-1

The unified type system from the Scala documentation

In the next chapter, you will be introduced to the various topics surrounding control flow. The difficulty level in the code and its subsequent capabilities will begin to increase dramatically. That being said, if you know string manipulation and comparison operators thoroughly, you will be extremely well positioned to comprehend the material in a fervent manner.

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

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