简体   繁体   中英

Go: Anonymous Function

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?

Short version

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

Longer version

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.

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))
}

Go Playground

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM