Order of Evaluation

We're now ready to tackle the question of order-of-evaluation. We might wonder if the order of evaluation even matters? In the examples we've looked at so far the order doesn't seem to matter, except for the issue that we cannot evaluate an expression before it's sub-expressions.

To investigate these issues further we need to introduce a new concept. So far we have almost always dealt with pure expressions. These are expressions that we can freely substitute in any order without issueFootnote corner-cases.

Impure expressions are those where the order of evaluation matters. We have already used one impure expression, the method draw. If we evaluate

Image.circle(100).draw
Image.rectangle(100, 50).draw

and

Image.rectangle(100, 50).draw
Image.circle(100).draw

the windows containing the images will appear in different orders. Hardly an exciting difference, but it is a difference, which is the point.

The key distinguishing feature of impure expressions is that their evaluation causes some change that we can see. For example, evaluating draw causes an image to be displayed. We call these observable changes side effects, or just effects for short. In a program containing side effects we cannot use substitution in any order we like. However we can use side effects to investigate the order of evaluation that Scala uses. Our tool for doing so will be the println method.

The println method displays text on the console (a side effect) and evaluates to () (the single value with type Unit). Here's an example:

println("Hello!")
// Hello!

The side-effect of println, printing to the console, gives us a convenient way to investigate the order of evaluation. For example, the result of running

println("A")
// A
println("B")
// B
println("C")
// C

indicates to us that expressions are evaluated from top to bottom. Let's use println to investigate further.

Exercise: No Substitute for Println

In a pure program we can give a name to any expression and substitute any other occurrences of that expression with the name. Concretely, we can rewrite

(2 + 2) + (2 + 2)

to

val a = (2 + 2)
a + a

and the result of the program doesn't change.

Using println as an example of an impure expression, demonstrates that this is not the case for impure expressions, and hence we can say that impure expressions, or side effects, break substitution.

Here is a simple example that illustrates this. The following two programs are observably different.

println("Happy birthday to you!")
// Happy birthday to you!
println("Happy birthday to you!")
// Happy birthday to you!
println("Happy birthday to you!")
// Happy birthday to you!
val happy = println("Happy birthday to you!")
// Happy birthday to you!
happy
happy
happy

Therefore we cannot freely use substitution in the presence of side effects, and we must be aware of the order of evaluation.

@:(Madness to our Methods)

When we introduced scopes we also introduced block expressions. A block is created by curly braces ({}). It evaluates all the expressions inside the braces. The final result is the result of the last expression in the block.

// Evaluates to three
{
  val one = 1
  val two = 2
  one + two
}
// res12: Int = 3

We can use block expressions to investigate the order in which method parameters are evaluated, by putting println expression inside a block that evaluates to some other useful value.

Using a method with multiple parameters (for example, Image.rectangle or Color.hsl) and block expressions, determine if Scala evaluates method parameters in a fixed order, and if so what that order is.

Note that you can write a block compactly, on one line, by separating expressions with semicolons (;). This is generally not good style but might be useful for these experiments. Here's an example.

// Evaluates to three
{ val one = 1; val two = 2; one + two }
// res13: Int = 3

@:@

The following code demonstrates that method parameters are evaluated from left to right.

Color.hsl(
  {
    println("a")
    0.degrees
  },
  {
    println("b")
    1.0
  },
  {
    println("c")
    1.0
  }
)
// a
// b
// c
// res14: Color = HSLA(
//   h = Angle(0.0),
//   s = Normalized(get = 1.0),
//   l = Normalized(get = 1.0),
//   a = Normalized(get = 1.0)
// )

We can write this more compactly as

Color.hsl({ println("a"); 0.degrees },
          { println("b"); 1.0 },
          { println("c"); 1.0 })
// a
// b
// c
// res15: Color = HSLA(
//   h = Angle(0.0),
//   s = Normalized(get = 1.0),
//   l = Normalized(get = 1.0),
//   a = Normalized(get = 1.0)
// )

@:(The Last Order)

In what order are Scala expressions evaluated? Perform whatever experiments you need to determine an answer to this question to your own satisfaction. You can reasonably assume that Scala uses consistent rules across all expressions. There aren't special cases for different expressions. @:@

We've already seen that expressions are evaluated from top-to-bottom, and method parameters are evaluated from left-to-right. We might want to check that expressions are in general evaluated left-to-right. We can show this fairly easily.

{ println("a"); 1 } + { println("b"); 2 } + { println("c"); 3}
// a
// b
// c
// res16: Int = 6

So in conclusion we can say that Scala expressions are evaluated from top-to-bottom and left-to-right.

[corner-cases] This is not entirely true. There are some corner cases where the order of evaluation does make a difference even with pure expressions. We're not going to worry about these cases here. If you're interested in learning more, and this is interesting and useful stuff, you can read up on "eager evaluation" and "lazy evaluation".