Reputation: 1265
Here is the code I've been trying to understand:
package main
import (
"fmt"
)
func squares() func() int {
var x int
return func() int {
x = x + 2
return x * x
}
}
func main() {
f := squares()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(squares()())
fmt.Println(squares()())
fmt.Println(squares()())
}
The result we got:
4
16
36
4
4
4
My question is: Why does the value of x
in fmt.Println(squares()())
stay unchanged?
Upvotes: 2
Views: 4907
Reputation: 13523
Simply because (1) x
is a captured closure for the anonymous function and (2) the default value for type int
is 0
. So every time you call it, you will see the same output.
Let's rewrite the function squares
like this:
func squares(initialValue int) func() int {
var x int
x = initialValue
return func() int {
x = x + 2
return x * x
}
}
Now for this:
func main() {
f := squares(0)
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(squares(0)())
fmt.Println(squares(0)())
fmt.Println(squares(0)())
}
We will see the exact output! Because we are initializing x
with 0
. If we use 1
as the initial value of x
:
func main() {
f := squares(1)
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(squares(1)())
fmt.Println(squares(1)())
fmt.Println(squares(1)())
}
We will see this result:
9
25
49
9
9
9
As you can see, it's just about the initial value of x
, which when not initialized explicitly, will have it's default value, zero.
Upvotes: 1
Reputation: 17604
Here's a different perspective to answer your question.
First break down the squares()
function first to understand what is going on:
func squares() func() int {
The above defines a func named squares
that returns another function of type func() int
(and that returns an int
, but that's not the focus here).
Drill that into your head first: it returns a function.
Now, let's see what happens when we call squares()
:
var x int
That's it. It defines a varibale x
, which defaults to value 0 per Go specs.
OK, now we have a variable in scope called x
and it has a value of 0. Now, we return a function:
return func() int {
x = x + 2
return x * x
}
If we had not defined x
earlier, this would be a build error because x must be defined. But, it was defined in the previous scope.
Go's use of closures allows another scope to be defined, which uses variables in the previous scope. var x int
in this case.
Also, you are able to modify closures (variables in previous scope). And in this case, we are modifying the previous var x int
that was defined in the previous scope.
Hold onto those thoughts for a moment, let's run some code...
f := squares()
Here we run squares()
, which defines var x int
as zero and returns a func()
named f()
that can do more work on x
.
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
Since we continue to reuse f()
, this keeps the scope of var x int
on a stack, and that stack remains around as long as you have this f()
variable. Therefore, x
continues to retain its modified value and be modified.
With all of this knowledge, the answer to your question is simple: if you don't retain the scope, the f()
definition like above, then you define a new var x int = 0
at every invitation of squares()
:
fmt.Println(squares()())
This invokes a new squares()
at every call. And therefore, a new var x int = 0
.
So the output of the returning func of squares, which is invoked with squares()()
, always operates on var x int = 0
, at every invokation since a new squares is called each time.
Upvotes: 2
Reputation: 11551
The reason you are getting the same result when you are calling the second variant with closure function is a consequence of scope rules. All function values created by the new self invoking, or anonymous function, capture and share the same variable - an addressable storage location, not it's value at that particular moment.
The anonymous function introduce a new lexical block which shares the same logical address to which the function values points to, so each time you invoke the function without to enclose the function to a new scope it will share the same logical address. This is the reason why you'll see in many places an anonymous function called in this way:
for i := range mymap {
func(n int) {
fmt. Println(n)
}(i) // note the scope
}
To address your issue one way would be to use pointer variables, this way you will be absolute sure that you will share the variable allocated to the same memory address. Here is the updated and working code:
package main
import (
"fmt"
)
func squares() func(x *int) int {
return func(x *int) int {
*x = *x + 2
return *x * *x
}
}
func main() {
f := squares()
x := 0
fmt.Println(f(&x))
fmt.Println(f(&x))
fmt.Println(f(&x))
fmt.Println(squares()(&x))
fmt.Println(squares()(&x))
fmt.Println(squares()(&x))
}
Another way is to expose the variable x
as a global variable. This will guarantee that you won't create a new variable x
each time you run the anonymous function.
Upvotes: 2
Reputation: 38809
You are building a new closure each time you call squares
.
This is exactly as if you built a new Counter object in an object-oriented language:
new Counter().increment(); // 4
new Counter().increment(); // 4
...as opposed to:
c = new Counter();
c.increment(); // 4
c.increment(); // 16
In your function, var x int
declares a local variable x
:
func squares() func() int {
var x int
return func() int {
x = x + 2
return x * x
}
}
As for any function, local variables are visible only inside the function. If you call a function in different contexts, each call has a separate set of memory storage addressable by your local symbols (here x
). What happens when you return a function is that any binding currently visible in the scope of your code is kept alongside your function, which is then called a closure.
Closures can hold a state, like objects do. Thus your closure can refer to the local variables that were visible when it was created, even when you escape the block where local variables were introduced (thankfully, the GC is here to keep track of the memory associated with those variables).
When you define f
, you create a fresh closure. Each time you call it you modify the same place referenced by the internal x
variable. But if you create fresh closures and call them once each, then you won't see the same side-effects, because each x
names a different place in memory.
Upvotes: 7