Expression-Oriented Programming (Scala 3 Video)
JIT: Expression-Oriented Programming (EOP)
Before we start writing some code, we need to look at one more JIT topic: Expression-Oriented Programming, or EOP. And to understand EOP, we need to be clear about the differences between expressions and statements in programming.
Statements vs expressions
In programming, a statement is a block of code that does not return a result, and is used solely for its side effect. For instance, both of these lines of code are statements because they don’t return a result:
println(a)
if a == b then println(a)
Lines of code like those are used solely for their side effects, and in those examples, their side effect is printing to STDOUT.
Conversely, a pure expression is a block of code that DOES return a result, and has no side effects. A terrific thing about Scala is that every line of code can be an expression. That’s because all of these Scala constructs return a result:
if
/then
/else
match
expressionsfor
expressions- Even
try
/catch
/finally
returns a value
To explain this, let’s look at how to use the if
/then
/else
construct as an expression.
Aside: A quick clarification
For the purists out there, it’s more accurate to say that this code doesn’t return a useful result:
if a == b then println(a)
It does return something — a type called Unit
— which is like void
in other languages. But in cases like this we generally don’t care about its return value.
As I mentioned previously, the
Unit
type has only one value (or instance) in Scala, and that value is represented by the()
symbol. Therefore, this type is not meaningful or informative, and we use it as a function’s return type when we have no interest in that return type.
Using ‘if’ as an expression
As mentioned, if
, match
, for
, and try
can all be used as expressions. In regards to if
, this means that if
/then
/else
yields a result:
val c = if a < b then a else b
That code can be read as, “If a
is less than b
, assign the value of a
to c
. If not, assign the value of b
to c
.” Because if
/then
/else
is an expression that returns a value, it’s also commonly used as the body of a function:
def min(a: Int, b: Int): Int =
if a < b then a else b
That code defines a function named min
that returns the minimum value of the two integer parameters that are passed into it, a
and b
.
Other constructs as expressions
Okay, while I’m in the neighborhood, let’s look at how Scala’s other constructs can be used as expressions. This example shows how a match
expression yields a value:
val evenOrOdd: String = i match
case 1 | 3 | 5 | 7 | 9 => "odd"
case 2 | 4 | 6 | 8 | 10 => "even"
Here’s a for
expression that yields a value:
val result =
for
i <- 1 to 10
if i > 5
yield
i
If you’re not familiar with that syntax, when that for
expression is run, result
will hold this value:
Vector(6, 7, 8, 9, 10)
Vector
is a specific type of Scala sequence — child ofSeq
— that has high-performance characteristics, and is commonly used in Scala.
When you first start with Scala you may prefer to explicitly declare result
’s data type, like this:
val result: Seq[Int] = // explicitly declare result’s type
for
i <- 1 to 10
if i > 5
yield
i
Notice that I added the type Seq[Int]
after the result
variable. Scala can usually figure those types out implicitly, but if you think the code is easier to read with the data type shown explicitly, by all means, add it.
Lastly, even try
/catch
/finally
blocks are used to write expressions:
def makeInt(s: String): Int =
try
s.toInt
catch
case e: Exception => 0
finally
// do whatever you need to do here
Note that this is NOT a recommended way to write this function — returning 0
when you get an exception is wrong, and I show better ways to write it shortly — but this does show that try
/catch
/finally
yields a value. You can use this makeInt
function like this:
makeInt("11") // 11
makeInt("eleven") // 0
What EOP code looks like
As I show in the Scala Cookbook, when you write code using EOP, your code becomes easy to write, easy to read, and easy to test. It’s just a series of expressions that use immutable variables, immutable data structures, and pure functions:
// a series of expressions
val symbol = "AAPL"
val url = buildUrl(symbol)
val html = getUrlContent(url) // an impure function
val price = getPrice(html)
val volume = getVolume(html)
val high = getHigh(html)
val low = getLow(html)
val date = getDate() // an impure function
// StockInstance is a class that takes these parameters:
val stockInstance = StockInstance(symbol, date, price, volume, high, low)
Even though that example uses two impure functions to communicate with the outside world, it gives you can idea of what EOP code looks like. (In regard to the getUrlContent
function, I’ll show a better way to write it in the following chapters.)
EOP benefits
Because expressions always return a result and generally don’t have side effects, there are several benefits to EOP:
- The code is easier to write, read, and test. Inputs go into a pure function, a result is returned, it’s assigned to an immutable variable, and there are no side effects.
- Combined with Scala’s syntax, EOP results in concise, expressive code.
- Although it’s only been hinted at in these examples, expressions can often be executed in any order. This subtle feature lets you execute expressions in parallel, which is a huge bonus when you’re trying to take advantage of multicore CPUs.
If you’re interested in more details, I write about all of these benefits in Functional Programming, Simplified (and ~40% of that book is free).
On to the code!
Given this background, it’s time to start writing some code that demonstrates the benefits of pure functions, immutable things, and EOP!
Update: All of my new videos are now on
LearnScala.dev