Welcome back to CS 124! You are not alone.
Today's lesson is a lot of fun. We'll spend some time on our first discussion of Kotlin internals. Specifically, we'll explain more about what happens when your code is run. Then we'll discuss immutability in Kotlin.
Our focus in this class is on teaching you to think computationally and express those thoughts coherently in the Kotlin programming language. Once you do that, you'll be able to learn other programming languages easily and accomplish other computational tasks.
So we don't spend a lot of time on exactly what is going on under the hood when Kotlin executes the code that you submit. On some level, it doesn't matter. As long as the core language constructs have a stable meaning, the internals of how things work don't matter. When I write:
I expect to see a message displayed 8 times. Kotlin could and might completely change exactly how that is accomplished, but as long as it is accomplished, I'm not going to notice. This is a powerful idea in computer science, even if at times the methods of implementation are somewhat less than practical.
But! In this case having a bit of an understanding of what is happening is actually useful. And, what is actually happening is quite interesting! So let's peek under the hood...
When you run a Kotlin program there are actually two distinct steps that are taking place:
Let's talk a bit about when compilation happens, what is produced, and the kind of errors that the compiler can produce during this step.
Now let's return to the same environment and discuss the execution step and the kind of errors that can occur at that point.
One of the reasons understanding these steps is so important is that they have a relationship to the difference between development and production:
Compiler errors generally only happen in development. That means that only developers see them! In contrast, runtime errors can happen in production! That means they might affect actual users.
Software developers acquire a high degree of patience with broken and crashy software. Users do not. That means that, if you can reduce runtime errors and catch them during development, you will produce better software.
Kotlin represents a new generation of compilers that harnesses the power of today's computers to help catch errors in your code before they affect users. So while Java allows you to do this—this code generates a runtime error:
Kotlin does not—this code generates a compiler error:
Before we conclude, we'll introduce an important idea in Kotlin: immutability.
We've discussed previously how to declare and initialize variables in Kotlin:
We've also pointed out that, due to Kotlin's powerful type inference, even the Kotlin tracks and enforces variable types, we usually don't need to explicitly provide types.
In the example above, Kotlin will figure out what type
i is from the type of the initial assignment.
8 is an
Int literal, so
i will be inferred to have type
We know this.
valand Immutable Variables
But what is this
var keyword that we are using above?
It seems somewhat unnecessary at this point.
Using it does help us distinguish between variable declaration and reassignment:
var actually hints at a deeper and more fundamental feature of Kotlin: immutability.
To see how this works, let's examine
In addition to
val to declare an immutable variable, Kotlin provides a number of other features to support immutability in our code.
Consider the following snippet:
isHot is entirely dependent on the value of
temperature, which in the code above is a
val and so cannot change.
But, because we need to set the value of
isHot inside an
if statement, we have to declare it as a
var so that we can change it in the
Happily Kotlin provides a very elegant way around this problem:
Previously we've seen
if expressions are very similar, except that they can be used on the right side of an assignment!
Let's explore this idea together:
if expressions turn out to be incredibly useful and powerful, and we'll use them whenever we need to initialize a variable based on a condition.
Not only are they clear and expressive, but they frequently allow us to mark the initialized variable as
val if it does not need to change after the assignment.
Kotlin's use of
val promote immutability to a first-class citizen in the language.
(Simliar behavior is possible in Java, which Kotlin interoperates with, through the
But it is less central to the design of the language.)
Each time you create a variable you must decide if it can be reassigned (
var) or not (
You may think that this is a bit strange. After all, it's a variable! Shouldn't its value be able to change? Sometimes change is necessary. But often it turns out not to be. And, by reducing the number of variables that can change in our programs, we end up writing code that is easier to understand.
Put it this way.
When I create and initialize a
val, I know immediately what it's value is and always will be.
In contrast, when I create and initialize a
var, I have to keep an eye out for any places that it's value could change unexpectedly in the code that follows.
That makes my code harder to understand.
Immutability is an important software design principle.
So while it may not seem completely clear why this is so important now, start today we'll try and take advantages of
val and Kotlin's other immutability features to reduce the amount of mutability in our code.