CBV and CBN
Call by Value vs. Call by Name: Which One is Faster?
Now that we understand evaluation strategies, let’s explore the two primary approaches:
- Call by Value (CBV): Evaluate arguments before calling the function.
- Call by Name (CBN): Delay evaluating arguments until they are actually needed inside the function.
To make this comparison concrete, let’s analyze the function given in the slide:
def test(x: Int, y: Int): Int =
x * x
This function only uses x — the parameter y is completely ignored.
Now, let’s evaluate different function calls using CBV and CBN and see which is faster.
Understanding the Key Difference
-
Call by Value (CBV):
- First, evaluate all arguments before executing the function.
- Replace function parameters with precomputed values.
- Arguments are evaluated only once.
-
Call by Name (CBN):
- Function arguments are not evaluated immediately.
- Instead, they are substituted as is and only computed when needed inside the function.
- If an argument is never used, it is never evaluated.
This means that if an argument is expensive to compute and not needed, Call by Name can save time by skipping unnecessary computations.
Evaluating Function Calls
test(2, 3)
- Call by Value:
- we do not have expressions to evaluate, so we can directly substitute
2forxand3fory. - Compute
x * ximmediately. - Result:
4.
- we do not have expressions to evaluate, so we can directly substitute
test(2, 3)
→ 2 * 2
→ 4
- Call by Name:
- in the body of the function, we only use
x, so we ignorey. - compute
x * xonly when needed (immediately in this case). - Result:
4.
- in the body of the function, we only use
test(2, 3)
→ 2 * 2
→ 4
So, in this case, both CBV and CBN produce the same result... but what if we had a more complex function?
test(6 + 9, 8)
- Call by Value:
- First, evaluate all arguments before calling the function.
- Compute
6 + 9 = 15and8remains8. - Now substitute
x = 15intox * xand compute.
test(6 + 9, 8)
→ test(15, 8) // argument (6 + 9) evaluated before function call
→ 15 * 15
→ 225
- Call by Name:
- The function body only uses
x, soyis ignored. - Instead of computing
6 + 9before calling the function, we substitute the expression as is. - Only when
xis used inx * x, do we compute6 + 9.
- The function body only uses
test(6 + 9, 8)
→ (6 + 9) * (6 + 9) // argument is substituted directly
→ 15 * (6 + 9)
→ 15 * 15
→ 225
- Key Difference:
- CBV computes
6 + 9once before calling the function. - CBN computes
6 + 9twice, because it substitutes(6 + 9)directly intox * x.
- CBV computes
✅ CBV is more efficient here because it avoids redundant calculations.
test(4, 2 * 10)
- Call by Value:
- Evaluate arguments before calling the function.
- Compute
2 * 10 = 20and substitutex = 4.
test(4, 2 * 10)
→ test(4, 20) // Arguments evaluated before function call
→ 4 * 4
→ 16
- Call by Name:
- The function never uses
y, so2 * 10is never evaluated. - Simply substitute
x = 4, and compute4 * 4.
- The function never uses
test(4, 2 * 10)
→ 4 * 4
→ 16
- Key Difference:
- CBV computes
2 * 10unnecessarily, even though it is never used. - CBN skips evaluating
yentirely, saving unnecessary computation.
- CBV computes
✅ CBN is more efficient here because it avoids computing 2 * 10, which is not needed.
test(3 + 2, 4 * 3)
- Call by Value:
- Compute both arguments before calling the function.
- Compute
3 + 2 = 5and4 * 3 = 12. - Substitute
x = 5and compute.
test(3 + 2, 4 * 3)
→ test(5, 4 * 3)
→ test(5, 12) // arguments evaluated before function call
→ 5 * 5
→ 25
- Call by Name:
xis used inx * x, so we delay evaluating3 + 2.- Instead of computing
3 + 2before calling the function, we substitute(3 + 2)directly. yis not used, so4 * 3is ignored.
test(3 + 2, 4 * 3)
→ (3 + 2) * (3 + 2) // argument is substituted directly
→ 5 * (3 + 2)
→ 5 * 5
→ 25
- Key Difference:
- CBV computes both
3 + 2and4 * 3before calling the function. - CBN computes
3 + 2twice, but skips evaluating4 * 3entirely.
- CBV computes both
✅ Both strategies are equally efficient in this case.
Conclusion
| Function Call | Call by Value | Call by Name | Same |
|---|---|---|---|
test(2, 3) |
✅ | ||
test(6 + 9, 8) |
✅ | ||
test(4, 2 * 10) |
✅ | ||
test(3 + 2, 4 * 3) |
✅ |
One more example
The previous examples focused on pure functions where evaluation order only affected performance. But what happens when a function has side effects, such as printing output? This is where the difference between Call by Value (CBV) and Call by Name (CBN) becomes even more obvious.
Let’s define a simple function that prints something when called and returns an Int:
def something(): Int = {
println("calling something")
1
}
Now, we define two functions that accept an Int argument:
callByValueuses Call by Value (x: Int).callByNameuses Call by Name (x: ⇒ Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
// note the `⇒` syntax for CBN!
def callByName(x: ⇒ Int) = {
println("x1=" + x)
println("x2=" + x)
}
Now, let’s see what happens when we call them with something().
Call by Value
callByValue(something())
Output:
calling something
x1=1
x2=1
something()is evaluated before callingcallByValue.- The return value (
1) is passed intox. - Both
printlnstatements use the same precomputed value.
Call by Name
callByName(something())
Output:
calling something
x1=1
calling something
x2=1
something()is evaluated each timexis used.- Since
xappears twice in the function body, the side effect (printing) happens twice. - The value of
xis recomputed every time it's accessed.
Key Takeaways:
- Call by Value evaluates arguments once before calling the function, making it more efficient when an argument is used multiple times.
- Call by Name recomputes the argument every time it's used, which can lead to unnecessary repeated work and side effects.
- If the argument involves side effects (e.g., printing or modifying a variable), CBN can cause unexpected behavior.
This example demonstrates why understanding when arguments are evaluated is crucial, especially when dealing with functions that produce side effects.
Source: CBN vs CBV, clarification needed