Introduction
In this lesson, we'll learn about boolean data and the comparison and logical operators, as well as beginning to learn about data injection into strings, and more. Hang in there, because there's a lot more to learn about Scala!
Learning Outcomes
By the end of this lesson, you should be able to:
- Describe what a comment is
- Describe what a type is
- Embed a value/variable/expression in a string
- Understand what a method is
- Name the three logical operators
- Understand what the comparison operators are
Comments
Comments are parts of your program that are never executed or compiled. They have no effect on your program whatsoever, and only exist to communicate with other programmers (and your future self).
A comment is added to your program in two different ways, by putting //
in front of some text, or surrounding a block of text with /**/
.
Here's an example of the //
comment.
scala> //hi everyone
This comment is one line only. It's usually used to add notes to your program.
scala> 4+5 //this will produce 9
val res0: Int = 9
scala> //see?
The block comment /**/
is used to add larger, multiline notes to your program, or add an inline note to some code.
scala> /*hello
world*/
scala> 4 /*this is the age of my daughter*/ + 5
val res0: Int = 9
Types
Scala is a statically, strongly typed language. Static typing means that every value has a type, and said type doesn't change for the entire program. Strong typing means that Scala is very unlikely to interpret one type of data as another, and will quickly throw a compilation error if you mix up your data types.
But what is a type? A type is a label for data that tells the programming language how to interpret it. As you may or may not be aware, all memory in your computer is a series of 0 and 1 values. These values can be interpreted as:
- numbers - 0b00000011 = 3
- letters - 0b01000001 = a and so on... Types tell the program how these 0s and 1s should be interpreted, and what can be done with them.
One way to think about types is like file extensions. .jpg
tells your computer that the file in question is a kind of image. .txt
tells your computer that the file is a text file. If you change the extension of a file, your computer will try to interpret the data in the file in a different way, sometimes with disastrous results. Types serve a very similar function in Scala, telling the programming language what the data is, how it should be interpreted, and how it can be used.
Int, the whole number datatype
Int
is one of the most common types in Scala, and it represents a whole number. Examples of Int
data are 1
, 7
, and -56
. 3.14
is not Int
data, as it is fractional.
If you use the division operator on two pieces of Int
data, you’ll get Int
data back. This means that division of Int
values cannot produce fractional results. For example:
val a = 1
println(a/2) //returns 0
val b = 4
println(b/2) //returns 2
Because Int
s must remain whole at all times, a division operation that yields a fractional result will be rounded down to the nearest whole number. In the above example, 1/2
would return 0.5
, but since Int
cannot store that data, 0
is the result.
Int
is a 32-bit datatype, meaning that it uses thirty-two 1s and 0s to record numeric information. The format it uses means that it can only store numbers of a limited size. In Scala, an Int
can not be greater than 2147483647
, or less than -2147483648
. If you add 1 to 2147483647
, you will end up with the result -2147483648
, and if you subtract 1 from -2147483648
you will get 2147483647
back.
Double, the fractional number datatype
Double
s are a type that represents fractional data. That is, they can be used to express numbers like 0.5
and 400
. Double
data is stored differently than Int
data, using 64-bits in a different format to describe numbers. Because of the differences in how it stores numbers, Double
s can represent much larger numbers than Int
s ever can. The largest number that can be stored in Double
format is 1.7976931348623157E308
; 1.7976931348623157 times 10 to the power of 308. The downside of the Double
format is precision. An Int
formatted data can represent every number in its range exactly. On the other hand, the Double
format can only approximately represent a good deal of numbers, meaning that after doing math with Double
s for a while, you will get less and less precise results. It is for this reason that if you do not need to express very large numbers or fractional numbers, you should use the Int
datatype instead of Double
.
String, the textual datatype
As explained in the last part, String
is the representation of textual data in Scala. In short, it describes a piece of text and it's a fundamental building block of the Scala language.
There are a number of ways to construct a String
in Scala. One of them involves surrounding text with double quotes ("
).
"Hello, World!"
Another involves the usage of triple quotes ("""
).
"""This is a triple quoted String. I can use "double quotes" in this string with no problem.
I can also have newlines in this String very easily."""
Triple quote String
s have advantages. The text you enter into the String
will look almost exactly like what will be output by your program. There are downsides to Triple quote String
s though. With regular String
s, you can use short hand text to represent certain abstract concepts. An example is the concept of a newline, which can be put into a normal string with \n
.
scala> "hello\nworld"
val res0: String = hello
world
Another example is tabs, configuration-based indentation of text. With a double quote string, you can insert a tab with \t
.
scala> "\thello world"
val res0: String = " hello world"
The size of a tab is controlled by whatever software is representing it. Some things make a tab equal to 4 spaces. Some things make it equal to 2 spaces. VSCode will allow you to configure the size of a tab to whatever you'd like it to be. This concept of tabs is wholly unrepresentable with triple quote String
s.
Boolean, the logical datatype
The Boolean type represents two possible pieces of data: true
and false
. This datatype is incredibly important to Scala, as it’s used to make logical decisions in a program.
Specifying the type of a Value
In order to specify the type of a value, you follow the name of the value with : TypeName
. For example:
val a: Int = 4
As before, we use the val
keyword to indicate we’re declaring a value, followed by the name of the value. What changes this time is that we follow the name with : Int
, which specifies that the value’s type is Int
. After that, we follow with the assignment operator and some data in order to define the value.
Type Inference
As I wrote before. Scala is statically typed. This means that every value that’s declared and defined has an associated type. However, in all previous examples we haven’t specified the types for our values.
These values had types too, but we didn’t need to specify their types because Scala has type inference. This allows Scala to deduce what type the value should be based on the data you define it with. For example:
val a = 4 //a has type Int
val b = true //b has type Boolean
Type inference lets us cut down on some of the boilerplate in our programs. It's immediately obvious when we write val a = 4
that a
should be the type Int
, since the type of the data defining it, 4
, is Int
. With type inference, we leave it to the Scala compiler to work out the type based on the data we've given it. That being said you can always specify what type a value is.
val a: Int = 3
Methods
In Scala, methods are program fragments that perform work upon being invoked. These program fragments are bound to types, and can only be invoked on data of the appropriate type. In order to invoke a method, one provides some data, followed by the .
operator, followed by the name of the method to invoke. An example of a method is the toString
method that exists for all types in Scala:
scala> 5.toString
val res0: String = 5
Here, we invoke the toString
method on the data 5
. The purpose of the toString
method is to convert some data into a String
. When invoked, it performs some work in the background to transform the original data into textual data. It is not necessary to know how this is done in order to use the toString
method, and that fact is one of the hallmarks of a good method.
Other methods exist in Scala that have similar purposes to toString
. For example, toInt
is a method that is attached to the String
type; any String data can have the toInt
method invoked on it to attempt to turn it into an Int
:
scala> "5".toInt
val res0: Int = 5
scala> val numberString = "15"
val numberString: String = "15"
scala> numberString.toInt //this works too
val res1: Int = 15
A similar method for turning String
s to Double
s is attached to the String
type:
scala> "4.0".toDouble
val res0: Double 4.0
toDouble
and toInt
is likewise attached to the Int
and Double
types, allowing one to convert an Int
to a Double
and vice-versa:
scala> 4.0.toInt
val res0: Int = 4
scala> 4.toDouble
val res1: Double = 4.0
Note that a Double
that is not whole when converted to an Int
will be truncated:
scala> 4.3.toInt
val res0: Int = 4
What happens when you try to convert a
String
that is not a textual representation of a whole number into anInt
?scala> "hello".toInt java.lang.NumberFormatException: For input string: "hello" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:668) at java.base/java.lang.Integer.parseInt(Integer.java:786) at scala.collection.StringOps$.toInt$extension(StringOps.scala:910) ... 28 elided
When converting
Strings
into number types, one must be careful, or your program will crash.
Method parameters
The behavior of the methods toString
, toInt
, and toDouble
only changes based on the values they're invoked from. "5".toInt
will always produce 5
and 4.toDouble
will always produce 4.0
. There are other methods that take input however, and such methods will produce different results based on this additional input. We call the input to a method a parameter, and it is passed to a method via the usage of parentheses.
scala> "hello world".take(3)
val res0: String = hel
scala> "hello world".take(5)
val res1: String = hello
Whether or not a method takes parameters depends on its definition.
Did you know you've been using methods since Part 1? Not all methods require the
.
operator to be invoked. For example,println
is short forSystem.out.println
, but doesn't require the.
operator to invoke it.scala> println("hello, world!") hello, world!
You've even used methods that don't require the
.
operator or parentheses to pass inputs to them. The operators discussed in Part 1 are themselves cleverly disguised methods:4.+(5)
Helpful string methods
There is a great deal of methods that exist on Strings
. The following list is a small selection of helpful methods. Try using them and seeing their effect on your Strings
:
size
- Returns the number of letters in the `Stringreverse
- Reverses aString
distinct
- Outputs each letter in theString
nonEmpty
- If theString
has text, returnstrue
, otherwise `falseempty
- If theString
has no text, returns true, otherwise falsetoLowerCase
- Makes theString
lower casetoUpperCase
- Makes theString
upper caselast
- Gives the last letter of a String
For a more complete listing of the methods available on String
, refer to The Java String API and The Scala String extensions.
String Interpolation
String interpolation is used to construct strings with values and calculations instead of using the concatenation operator.
Let’s say you wanted to have a program that output a user’s name and age. With concatenation you would write something like:
val name = “Trixie”
val age = 23
println(“The user’s name is “ + name + “ and they’re “ + age + “.”)
Using concatenation, we have to be careful to leave trailing spaces so that our text is formatted nicely, and we have to open and close the string multiple times. This can be error prone.
String interpolation allows us to more directly insert data into Strings:
val name = “Trixie”
val age = 26
println(s”The user’s name is $name and they’re $age.”)
The s
in front of the double quotes activates string interpretation. Once that’s added, anything with a $
in front of it is interpreted as a value name, and said value’s contents will be injected in place of the name and the $
symbol.
String interpolation works with both double quotes and triple quotes strings.
Comparisons
Comparison is the act of determining something's relationship to something else. There are many comparisons that can be made between types of data, but there is a set of common ones that are used almost universally in programming:
<
- Less than==
- Equal to!=
- Not equal to>
- Greater Than>=
Greater than or equal to<=
Less than or equal to
If you've ever done any math in school, most of these should be familiar to you. They are all binary operators; they take two pieces of data and return data of the Boolean
data type. That is, they will return true
or false
to you when used.
scala> 4 > 2
val res0: Boolean = true
scala> 4 < 2
val res1: Boolean = false
scala> 5 == 5
val res2: Boolean = true
scala> 2 != 3
val res3: Boolean = true
If you've read through the methods section of this part, you may be wondering if these binary operators are cleverly disguised methods that take one parameter:
scala> 4.<(5)
val res0: Boolean = true
These comparison operators are extremely useful for working with data, and are defined for all of the types we've discussed so far.
scala> 3.0 < 5.0
val res0: Boolean = true
scala> false < true
val res1: Boolean = true
String
s to can be compared with these operators, though their comparison is less intuitive than the previously mentioned types. String
s use lexicographic ordering. When you ask if one String
is less than another, the computer will check letter by letter and answer based on the position in the alphabet of the letters being compared. Let's see what this looks like by seeing if "Hola"
is less than "Hello"
.
- First we compare the letter
H
to the letterH
. H
is equal toH
so we go to the next letter.- Now we compare the letter
e
to the lettero
. o
is greater thane
so we stop here- Since
Ho
is greater thanHe
,"Hola"
is not less than"Hello"
- The operator returns
false
Note that lexicographic ordering considers capitalized letters to be less than uncapitalized letters. Don't let this trip you up!!
And, Or, and Not
For the Boolean
type, there exists three very important operators:
&&
- And||
- Or!
- Not
These three logical operators allow you to make logical calculations with Boolean
data.
And
The and operation is represented by the &&
operator in Scala. &&
is a binary operator that takes two Boolean
values and returns true
if both its inputs are true
. Otherwise, it returns false
.
scala> true && false
val res0: Boolean = false
scala> true && true
val res1: Boolean = true
scala> false && true
val res2: Boolean = false
scala> false && false
val res3: Boolean = false
The and operation is very useful if you want to know if two or more things are true
at the same time.
Or
The or operation is represented by the ||
operator in Scala. ||
is a binary operator that takes two Boolean
values and returns true
if any of its inputs are true
. Otherwise, it returns false
scala> true || false
val res0: Boolean = true
scala> true || true
val res1: Boolean = true
scala> false || true
val res2: Boolean = true
scala> false || false
val res3: Boolean = false
The or operation is useful if you want to know if at least one thing is true.
Not
The not operation is represented by the !
operator in Scala. !
is a unary operator that goes in front of the data you want to apply it to. When applied to Boolean
data, it inverts it, turning true
to false
and false
to true
.
scala> !false
val res0: Boolean = true
scala> !true
val res1: Boolean = false
Logical and comparison operators in use
Logical and comparison operators are frequently used together, and can be used to express complex logic. As an example, if we wanted to test if 4 is less than 3 but greater than 1, we could write:
scala> 1 < 4 && 4 < 3
val res0: Boolean = false
Can you write an expression that tests if a String is equal to "cat" or less than "Dog"?
Practice
To give you a good bit of practice, I've created some scastie.scala-lang.org exercises for you to play with. It's best that you practice programming on your own computer rather than in an online environment, but we'll reach that point soon enough.
Be sure to do the lessons in order presented here. Pressing "run" at the top will run the code. The code will be executed in your browser and output will appear in the Javascript Console. Read all directions, watch the terminal, and read any errors. Don't forget to use println
extensively.