home search about rss feed twitter ko-fi

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 expressions
  • for 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 of Seq — 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