Reputation: 1629
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?
Upvotes: 1
Views: 371
Reputation: 3541
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.
Upvotes: 1
Reputation: 1230
i -= 1 takes the variable i and reassigns it to its value decremented by 1. Your body is referencing the same i variable, which is being modified everytime body is called. Ignoring all the recursion and your whileLoop essentially it is doing this:
var i = 10
println(i) // prints 10
i -= 1
println(i) // prints 9
i -= 1
...
i -= 1
println(i) // prints 1
i -= 1
println(i) // prints 0
Upvotes: 1
Reputation: 2804
When you declare a parameter with a type => Type
you are declaring that parameter as an anonymous function (a function that only returns that Type
without any input).
So when the function is called the first time, each parameter is evaluated for that particular value of i
each time.
As body
changes the i
value with each iteration, the program will reevaluate i
each time body
changes it.
I know it sounds complex, but bear with me. Lets see what happens when you remove the =>
.
If you remove the =>
you are not declaring anonymous functions to be reevaluated. You are defining parameters that won't be able to be rewritten. And as the condition can't be reevaluated each time, you will have an infinite bucle.
I hope this explanation gives some help.
Upvotes: 1