1. Overview

In this tutorial, we’ll learn about the features and evaluation strategies of call by-value and call by-name in Scala and how we can use them in practice.

2. Call By-Value

Function arguments are considered call by-value by default in Scala. Let’s define a function, test, which takes an argument as call by-value:

def test(a: Int) = a * a

In general, applications of parameterized functions are evaluated similarly as operators. First, it evaluates all the function arguments, from left to right. Then it replaces the function application by the function’s right-hand side, and, at the same time, it replaces the formal parameters of the function by the actual arguments. The call by-value strategy has the advantage that it evaluates every function argument only once.

3. Call By-Name

To make an argument called by-name, we simply prepend => (rocket symbol) to its type.

Let’s define a function, test, which takes an argument as call by-name:

def test(a: => Int) = a * a

Call by-name evaluation is similar to call by-value, but it has the advantage that a function argument won’t be evaluated until the corresponding value is used inside the function body.

Both strategies are reduced to the final value as long as:

  • The reduced expression consists of pure functions
  • Both evaluations terminate

4. Examples

Let’s define a function, addFirst, which takes an argument x of type Int as a call by-value and an argument y of type Int as a call by-name:

def addFirst(x: Int, y: => Int) = x + x

Now, let’s understand the evaluation of the function addFirst as a call by-value:

assert(addFirst(3+5, 7) == 16)

In this case, the call by-value strategy will first evaluate the expression 3+5 and then pass the value 8 to the function’s body where x parameter is added to itself to evaluate the final value of 16.

Now, let’s look at the same example as a call by-name.

assert(addFirst(7, 3+5) == 14)

In case, the call by-name strategy will ignore expression 3+5 because parameter y is not used inside the function’s body. Therefore, the parameter x will be added to itself producing the final value of 14.

In this example, the call by-name strategy is one step faster than the call by-value strategy.

Now, let’s take one more example. Let’s define an infinitely recursive function infinite():

def infinite(): Int = 1 + infinite()

If we apply this function as the x parameter in addFirst, the call by-value argument will produce a StackOverflowError:

assertThrows[StackOverflowError] {
  addFirst(infinite(), 4)
}

In the above example, simply calling the function produced a StackOverflowError because infinite() is evaluated before the function body is evaluated.

Now, if we use infinite() as the call by-name argument:

assert(addFirst(4, infinite()) == 8)

The infinite() function is never evaluated in the function body. Therefore, the function call runs successfully.

5. Call By-Value or Call By-Name

Using call by-name may seem like a more efficient solution since it’s possible that the argument is not evaluated.

However, call by-value is often more efficient than call by-name because it avoids the repeated re-computation of argument expressions that call by-name entails. Additionally, it can avoid other side effects because we know when the expressions will be evaluated.

6. Conclusion

In this tutorial, we introduced call by-value and call by-name in Scala. We’ve seen some of their features, usage, and evaluation strategies. As usual, the full source code can be found over on GitHub.


» 下一篇: Scala 字符串简介