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?
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.