Daniel Ciocîrlan
4 min read •
Share on:
This article is a bit shorter than usual, but I hope it will share a different kind of insight. We’re not going to explore new features, external libraries, create apps or demos. We’re going to take a look at some core Scala constructs and understand them in a different context.
When we want to compute a value, we can think of two different aspects to how that value is computed. There are more, but I will focus on the following:
The time aspect divides computations into now vs later. The memory aspect divides computations into “memoized” and non-memoized. I put “memoized” in quotes, because memoization is considered to be an optimization technique. However, at a foundational level, storing vs. not storing is a fundamental aspect of any computation.
In this classification, these two orthogonal aspects give us 4 kinds of computations:
Arguably, type 3 isn’t useful: computing a value immediately (i.e. at the point of definition) without storing the value is pure waste. So I’ll consider the last type (computed later, non-memoized) as “type 3”.
How does Scala implement this classification? Here’s a quick breakdown.
Type 1: How do we define a value that we compute right now and store it in memory? By defining a val
:
val meaningOfLife = 40 + 2
When we define a val
, it will always be computed at the point of definition. The expression is evaluated and the result is stored.
Type 2: How do we define a value that’s computed later, but stored when computed? Scala has a construct called a lazy val
:
lazy val complexThing = (1 to 42).map(_ => 1).reduce(_ + _)
This value is defined, but the expression will only be evaluated at the point of use. Once evaluated, it’s stored, so we can reference (and reuse) that value every time after that. Note that the question of “computed later” does not refer to asynchrony. When we define a Future, for example, the Future itself is available (as an instance of a type), and it’s its internal mechanism that decides to use threads, fetch results asynchronously, etc.
Type 3: How do we define a value that’s computed later, but not stored, i.e. if we want to use it again, we’ll have to recompute it? It’s a def
:
def recomputed = 3 + 39
This is potentially the most surprising. But it’s true: every time we use recomputed
, the expression is evaluated again!
So we have the following associations:
val
lazy val
def
But since our programs are not built just out of plain values, but also functions taking arguments, we can also transfer this small classification to arguments as well:
Scala is brilliant on so many levels, but this particular aspect of Scala makes it so powerful, because it leverages concepts we (as programmers) are familiar with in different contexts, and gave them new meanings. We think of
val
s as constantslazy val
s as constants computed laterdef
s as methodsbut how many times do think about what computations expressed in these terms are?
Share on: