Scala parameters evaluation strategies

on waitingforcode.com

Scala parameters evaluation strategies

In one of previous posts we've discovered Scala's laziness expressed with lazy operator. However it's not a single solution to implement it. Another one uses evaluation strategies covered in below paragraphs.

The first section of the post talks about evaluation strategies in programming. The next one focuses on one of Scala's most commonly used strategy called call-by-value. The third part is about less common approach known as call-by-name.

Evaluation strategies in programming

Evaluation strategy defines when parameters are evaluated during function calls. We distinguish 2 strategies: strict and non-strict (lazy). The former one evaluates the parameter before the call. The latter strategy is lazy, i.e. it evaluates the parameter only when it's really invoked.

Non-strict evaluation seems to be a good candidate for "caching" expensive operations up to the moment of their first use. Unfortunately it's not the case since the parameter will be evaluated every time when it's called. Unless the language provides lazy evaluation sub-strategy called call-by-need. This kind of evaluation guarantees the parameter computation at most once when it's really needed. Obviously, it applies only when the parameter is a pure function (= must always return the same value and not have side effects). Haskell is one of the languages supporting this evaluation strategy.

Call-by-value in Scala

As told in the introduction, call-by-value is probably the most common way to pass parameters to methods. It's a type of strict evaluation strategy that computes the value of passed object before function's call. As we can easily deduce, it brings a simplicity and guarantees only 1 evaluation. However it doesn't prevent against the evaluation when given value is not needed:

private val invocationCounter = new mutable.HashMap[String, Int]().withDefaultValue(0)

def getterOf3(methodName: String): Int = {
  invocationCounter.put(methodName, invocationCounter(methodName) + 1)
  3
}

"call-by-value" should "evaluate eagerly" in {
  val methodName = "eager_evaluation"
  def addTo3(valueFromGetterOf3: Int): Int = {
    val result1 = valueFromGetterOf3 + 2
    val result2 = valueFromGetterOf3 + 1
    result1 + result2
  }
  addTo3(getterOf3(methodName))

  // We expect the method providing the parameter to be evaluated only
  // once, before the execution of addTo3 function
  invocationCounter(methodName) shouldEqual 1
}

Call-by-name in Scala

An example of non-strict evaluation in Scala is call-by-name. Here the parameter is evaluated every time when it's really invoked. And as told, the evaluation can happen multiple times:

"call-by-name" should "evaluate lazily" in {
  val methodName = "lazy_evaluation"
  def addTo3(getter: => Int): Int = {
    val result1 = getter + 2
    val result2 = getter + 1
    result1 + result2
  }
  addTo3(getterOf3(methodName))

  invocationCounter(methodName) shouldEqual 2
}

Scala doesn't come with a native implementation of call-by-need. It's still possible to implement it in our own though. Tudor Zgureanu in the Adding support for call-by-need (aka lazy arguments) using scalameta demonstrated how to do it with Scalameta library.

This post composed of 3 parts presented parameters evaluation strategies. From the first part we've learned about 2 available evaluation strategy families: strict and non-strict. Scala comes with one method for each of them: call-by-value and call-by-name. The former one evaluates the parameter value before its real use. So it may be inefficient for time or resource-consuming operations where the parameter is eventually used. But unlike the latter method, call-by-value evaluates only once so it may perform much better when such consuming operation is really used in the invoked function.

Share, like or comment this post on Twitter: