After reading this lesson, you’ll be able to
Scala has a clear separation between mutable and immutable assignments. In Scala, values are immutable: you cannot modify them after creating them. Variables are mutable: they can refer to different instances over time. Deciding when to declare a value rather than a variable is essential for your code to be fast and bug-free. Variables are more straightforward to use in your code because you can modify them. However, they can make your program extremely difficult to maintain and lead to errors when different processes try to do so simultaneously. Values can be challenging to use because you cannot modify them once created. But they can make your program easier to understand: their assignments never change, so you can easily predict and test their evaluation. They also guarantee that your code will not be affected by concurrency issues, such as data inconsistencies, resources starvation, and deadlocks, when accessed by several threads. A fragment of code that multiple processes can access without causing concurrency issues is thread-safe. In the capstone for this unit, you’ll define both values and variables to name fragments of code and make your program more readable.
Assignments improve your code’s readability by breaking down the computation and assigning meaningful names to it. They can also increase your program’s performance by saving intermediate results that you can reuse and share between processes. Values are the most commonly used type of assignments in Scala.
In the next example, you’ll see how values can improve your code’s readability, particularly when dealing with complex scenarios. Suppose you are performing some calculation to mark exams with the following requirements: each exam has three questions, each with a possible score of 3 points, and its final mark must be between 1 and 10. Several solutions are possible; let’s pick one of them. You could calculate the average score for all the questions and then convert it to a number from 1 to 10. For example, you should follow these steps for three questions scoring 1.5, 2, and 2.5:
You compute the average score for all the questions: (1.5 + 2 + 2.5) / 3 is 2.
You scale the average score from 1 to 10. Considering that the maximum score possible for each question is 3, then you can rescale it as follows: 2 * 10 / 3 is about 6.6.
Let’s translate this procedure into code.
def markExam(q1: Double, q2: Double, q3: Double) = Math.round(((q1 + q2 + q3) / 3) * 10 / 3)
The function markExam
replicates the calculations described, but it is difficult to identify its objective and computation steps. You can improve the readability of the markExam
function by using values with descriptive names.
def markExam(q1: Double, q2: Double, q3: Double) = { val avgScore = (q1 + q2 + q3) / 3 val scaledScore = avgScore * 10 / 3 Math.round(scaledScore) ❶ }
❶ No need for the return keyword; the last expression is the one returned.
Values are a fundamental component of your code. In Scala, you declare them by using the keyword val. You can only initialize them once because they are immutable; if you try to reassign them, the code will not compile. Consider the following example:
scala> val a = 1 val a: Int = 1 scala> a val res0: Int = 1 scala> a = 2 <console>:11: error: reassignment to val ❶
❶ You cannot reassign a value, which is an immutable assignment.
When declaring a value, its type is optional; the compiler tries to infer it for you. If the compiler infers an unexpected type, you can provide it explicitly; the compiler will check if the type you specified is compatible with the assignment of your value and eventually return a compilation error:
scala> val b: Int = 1 val b: Int = 1 scala> val c: Double = 1 ❶ val c: Double = 1.0 scala> val d: Int = "not-an-int" ❷ error: type mismatch; found : String("not-an-int") required: Int scala> val d: Any = "not-an-int" ❸ val d: Any = not-an-int
❶ The compiler knows how to fit an Int into a Double.
❷ You cannot assign a String to an Int.
❸ Any is the root of the class hierarchy: you can assign any value to it.
Look at figure 4.1 for a syntax diagram on values in Scala.
In the previous section, you wrote a function to mark an exam. Let’s expand it so that you can compute the average score of all exams you marked so far. Values are immutable, so you cannot use them to keep track of how your stats evolve, which is the mutable state of your program. In these cases, you can use variables.
var marksSum = 0 ❶ var marksCount = 0 ❶ def averageMark: Double = marksSum.toDouble / marksCount ❷ def markExam(q1: Double, q2: Double, q3: Double): Int = { val avgScore = (q1 + q2 + q3) / 3 val scaledScore = avgScore * 10 / 3 val mark = Math.round(scaledScore).toInt ❸ marksSum += mark marksCount += 1 mark ❹ }
❷ When converting marksSum to Double, the result of your division must be a Double. An integer divided by another integer produces an Int while diving a double by an integer returns a double.
❸ Math.round results in a constant of type Long, so you need to convert it to Int.
❹ There’s no need for the return keyword; the last expression is the one returned.
In Scala, variables are very similar to values: while you can assign values only once, you can reassign variables multiple times. Consider the following example:
scala> var a = 1 var a: Int = 1 scala> a val res1: Int = 1 scala> a = 2 // mutated a
You do not need to provide a type for your variable; the compiler will infer it for you. When doing so, the compiler makes sure that the assignment is compatible with it. Look at figure 4.2 for a summary of how to define variables in Scala.
In this lesson, my objective was to teach about the different types of assignments in Scala.
You have learned how to declare values and that you can only assign them once.
You have seen how to define variables and how you can reassign them multiple times.
Try This Modify your markExam
function to keep track of both the lowest and the highest mark computed so far.
Quick Check 4.1 The operators +=
and -=
cannot be used with val:
a value is immutable and once assigned you cannot reassign it.
Quick Check 4.2 The first and third expressions are valid, while the second one is invalid:
Valid: an expression of type Double
is compatible with Double.
Invalid: the compiler doesn’t know how to fit an expression of type Double
into an Int
. Even if the quantity 2.0 is equal to 2 from a mathematical perspective, the compiler doesn’t automatically convert a Double
to Int
to avoid precision loss; 2.5 is not equivalent to neither 2 or 3. Note that the opposite (i.e., assigning an Int
expression to a Double
) is valid.
Valid: Any
is compatible with any type because it is at the root of Scala’s class hierarchy.
Quick Check 4.3 You can use the operator +=
together with a var
of type String
; you can reassign a variable, and the class String
has an implementation for the function +
. You cannot use the operator -=
with a var
of type String
because the class String
doesn’t have a method called “-
”.
3.134.118.95