Evaluation Strategies


Understanding Evaluation Strategies

Before diving into CBV and CBN, let’s step back and understand why evaluation strategies matter.

When a function is called in a programming language, how and when its arguments are evaluated can affect:

Expressions specified by methods are calculated in the same way as operators:

  1. Evaluate all function arguments in a specific order. i.e., from left to right.
  2. Replace the function name with its definition.
  3. Substitute the argument values for the function’s parameters.
  4. Compute the result step by step.

This is known as function application, and it follows a structured process. To make this more concrete, let’s break down an example.


Example

Consider the function:

def sumOfSquares(x: Double, y: Double): Double = square(x) + square(y)

And let’s evaluate the expression:

sumOfSquares(3, 2 + 2)

At first glance, this might seem trivial, but the order in which we evaluate things makes a huge difference. Let’s go step by step.


Step 1: Expanding the Function Definition

We replace the function call with its definition:

sumOfSquares(3, 2 + 2)
→ square(3) + square(2 + 2)

At this stage, we have a choice:

This choice defines how the evaluation strategy works.


Step 2: Expanding the First Function Call

Let’s focus on square(3):

def square(x: Double): Double = x * x

So,

square(3) + square(2 + 2)
→ (3 * 3) + square(2 + 2)
→ 9 + square(2 + 2)

At this stage, we still haven’t evaluated 2 + 2 yet. Again, the question arises:


Step 3: Expanding the Second Function Call

Now, let’s expand square(2 + 2):

9 + square(2 + 2)
→ 9 + ((2 + 2) * (2 + 2))

If we now compute 2 + 2:

9 + (4 * 4)

And finally:

9 + 16
→ 25

At this point, we’ve completed our evaluation — but notice something interesting:
We delayed evaluating 2 + 2 until the very last moment when it was absolutely necessary.


Changing the evaluation strategy

So we still have the same function:

def sumOfSquares(x: Double, y: Double): Double = square(x) + square(y)

// calculate the sum of squares
sumOfSquares(3, 2 + 2)

But now, let’s evaluate it with a different strategy:

Reducing arguments first

We had a choice in the previous example:

This time, let’s evaluate 2 + 2 first!

sumOfSquares(3, 2 + 2)
→ sumOfSquares(3, 4)

Expanding the function

Now, we expand the function:

sumOfSquares(3, 4)
→ square(3) + square(4)

Performing the calculations

Finally, we calculate the squares:

square(3) + square(4)
→ 9 + 16
→ 25

By evaluating 2 + 2 first, we were able to reduce the expression to a simpler form. So why we can’t always do this?


Why Does This Matter?

This example may look straightforward, but what if:

Depending on how we choose to evaluate arguments, these scenarios could either be efficient and correct or cause serious performance issues — or even non-termination.

Evaluation strategies determine when computations happen and whether they happen at all. This is the foundation of function application, and it’s the key to understanding different ways languages handle execution.

From here, we can now explore the two main strategies:
Call by Name (delay computation until needed) and Call by Value (compute first, use later). And this stuff will be crucial as we dive deeper into functional programming concepts. It can explain what happens when an argument is an infinite computation?