scalarecursiondslcallbyname

Dynamically changing functions scala


I'm learning scala and I have come across the following code.

def whileLoop(cond: => Boolean)(body: => Unit): Unit =
    if (cond) {
      body
      whileLoop(cond)(body)
    }
  var i = 10
  whileLoop (i > 0) {
    println(i)
    i -= 1
  }

The output is the numbers 10 to 1.

So both cond and body are "call by name" parameters. Which means they are evaluated when used in the function. If I understand that correctly. What I don't understand is how the body

println(i)
i -= 1

changes for each level of recursion that is applied the body is changing as the variable i is changing. But how does that exactly work? Each time the same function body is passed, to me this function stays the same but running the program shows me otherwise. I know that the function is evaluated each time but I don't understand how the i variable inside changes each time so can someone explain me how that works?


Solution

  • In this example, the body

    println(i)
    i -= 1
    

    is a closure that operates on the variable i which is in the scope of the body's definition. Hence i is not a local variable of the body, meaning that the operation -= modifies the only existing value i, not a local copy that gets discarded after the method invocation.

    The same is true for the condition: It is a closure that captures the same variable i, hence after each execution of the body, the condition will see the now updated value of i.

    Let us rewrite the example slightly without altering the meaning: Firstly, we can rewrite the whileLoop to take functions as arguments instead of call-by-name parameters:

    def whileLoop(cond: () => Boolean)(body: () => Unit): Unit =
      if (cond()) {
        body()
        whileLoop(cond)(body)
      }
    

    This rewritten whileLoop is semantically identical since a call-by-name argument is passed as an expression instead of the expression's evaluation. Disclaimer: I do not know if there are technical differences, e.g., regarding performance.

    Secondly, we can make the expressions that are passed for cond and body functions that take no argument:

    val condDef = () => i > 0
    
    val bodyDef = () => {
      println(i)
      i -= 1
    }
    

    Since both of them reference the variable i that is neither part of their parameters nor defined inside their body, we must put i in their scope.

    def main(args: Array[String]) {
      var i = 10
    
      val condDef = () => i > 0
    
      val bodyDef = () => {
        println(i)
        i -= 1
      }
    
      whileLoop (condDef) {
        bodyDef
      }
    }
    

    So i is accessible from both, condDef and bodyDef and is accessed and modified when they are evaluated.