Getting Started with Go Language: Closure Implementing the Fibonacci Sequence Generator
Following the previous articleMaps word counting exercisesAfter this, another classic exercise in the introduction of Go language – using closure to implement the Fibonacci sequence generator. This question can not only consolidate the core usage of closed packages, but also deeply understand the characteristics of ‘function as the return value’ in the Go language. This article will record the training requirements, implementation ideas, code analysis, and the core principles of closures in detail, as their own learning archive, and also for the reference of Go language self-study.
1. Description of exercise requirements
The core requirement of this exercise is to implement a function called fibonacci, which does not accept any parameters, but returns a function ( That is, the closure); each time the returned closure function is called, it will return a continuous Fibonacci number. The law of the Fibonacci sequence is: 0, 1, 1, 2, 3, 5, 8, 13, … (Starting from item 3, each one is equal to the sum of the first two items).
The exercise provides the basic code framework, which requires the improvement of the Fibonacci function, and finally through the circular call in the main function, the first 10 Fibonacci numbers are output to verify the correctness of the implementation.
The initial basic code is as follows:
package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
Second, the core ideas
To achieve this requirement, the core is to use the closure feature of the Go language – the closure can ‘capture’ the variables in its external functions (Fibonacci function), and keep the states of these variables. Every time the closure is called, it will continue to calculate based on the last state. The specific idea is divided into 3 steps:
- Inside the Fibonacci function, define two variables, which are used to hold the first two terms of the Fibonacci sequence (the initial value is 0 and 1, and the first two numbers corresponding to the corresponding sequence);
- Inside the Fibonacci function, define an anonymous function (ie a closure) that is responsible for calculating and returning the current Fibonacci number, and updating the states of the two external variables at the same time (let it point to the next two items of the sequence);
- The fibonacci function returns this anonymous closure, and each subsequent call to the closure will return the next Fibonacci number based on the previous state.
The key point: the external variables captured by the closure (the first two items of the Fibonacci sequence) will not be Destroyed, but will continue to be held by closures, and each call closure will update and keep this state – this is also the core of the realization of ‘continuously generate Fibonacci numbers’.
3. Complete code after improvement
Combined with the above ideas, the improved code is as follows, and the key steps and core logic have been added with detailed comments, which is convenient for understanding and reusing:
package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
// fibonacci 函数返回一个闭包,用于生成连续的斐波那契数
func fibonacci() func() int {
// 闭包会保留这两个变量的状态
a, b := 0, 1
return func() int {
// 先保存当前 a 的值(要返回的值)
res := a
// 更新 a 和 b 到下一个状态
a, b = b, a+b
// 返回结果
return res
}
}
func main() {
f := fibonacci() // 创建一个闭包实例
// 循环调用 10 次,输出前 10 个斐波那契数
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
4. Verification of running results
After running the above code, the console will output the first 10 Fibonacci numbers, the result is as follows, exactly as expected:
/app/go-tour/exercise-fibonacci-closure # go run main.go
0
1
1
2
3
5
8
13
21
34
If this result is output after running, it means that the implementation of the fibonacci function is completely correct, the closure successfully retains the variable state, and realizes the function of continuously generating the Fibonacci number.
In this exercise:
- The external function is fibonacci(), and the variables a and b are defined internally;
- Anonymous functions (closures) inside capture A and B;
- When fibonacci() is executed, A and B are not destroyed, but are continuously held by closures;
- Every time a closure is called (that is, f()), the states of A and B are updated to achieve ‘continuous calculation’.
- The core logic of the state update is the most critical code in the exercise is the state update inside the closure: a, b = b, a+b, here you need to pay attention to the execution order to avoid logical errors:
- First save the current value of A (res := A), because A is the Fibonacci number to be returned this time;
- Update a and b again: assign a value to the original b, and assign b to the original a + The original B (Note: the a and b here are both pre-updated values, because the assignment statement in the Go language is ‘parallel assignment’, first calculate the values of all the expressions on the right, and then assign them to the left).
- Finally return to res, complete a call.
We can disassemble the execution process of the first 5 calls, and more intuitively understand the status update:
- Initial state: a=0, b=1
- The first call f(): res=0 → A is updated to 1, and B is updated to 0+1=1 → return 0
- The 2nd call f(): res=1 → A is updated to 1, and B is updated to 1+1=2 → return 1
- The 3rd call f(): res=1 → A is updated to 2, and B is updated to 1+2=3 → back 1
- The 4th call f(): res=2 → A is updated to 3, and B is updated to 2+3=5 → return 2
- The 5th call f(): res=3 → A is updated to 5, and B is updated to 3+5=8 → return 3
- function as return value
In the Go language, the function is ‘first-class citizen’, which can be passed as a parameter, or it can be returned as a return value. In this exercise, the return value type of the Fibonacci() function is func() int, which means ‘a function with no arguments and an int type’, which is also the basis for the closure to be returned and called.
Note: Every time Fibonacci() is called, a new closure instance is created and a new set of A and B variables is captured (the initial values are both 0 and 1). That is, if we create another closure instance f2 := fibonacci(), calling f2() will regenerate a set of Fibonacci numbers, which does not affect the state of f().
5. Reminders of common error-prone points
When implementing this problem, it is easy for beginners to make two mistakes. Here is a special reminder to avoid stepping on the pit:
- Sequential error: update A and B first, and then save RES. This will lead to the first return not 0, but 1, destroying the initial law of the Fibonacci sequence;
- Variable scope error: Define a and b inside the closure, not inside the external function. In this way, every time the closure is called, A and B will be re-initialized (change back to 0 and 1), and the state cannot be reserved, and each time it returns 0.
6. Learning experience
This closure exercise tests the understanding of the Go language characteristics more than the MAPS exercise of the previous article – the ‘state retention’ feature of the closure is many advanced The basics of usage (such as generators and decorators may be a bit abstract at first understanding, but by dismantling the execution process and debugging the code, you can slowly master it.
For a developer who is self-learning Go language like this, this question makes me understand that closures are not ‘or isolated functions’, it is closely related to external variables, which is equivalent to a ‘function with memory’. This feature simplifies code logic, avoids the use of global variables (global variables can contaminate the scope), and implements ‘state multiplexing’.
In addition, through this question, I have also deepened my understanding of ‘functions as return values’ and realized the flexibility of functions in the Go language – not only can execute logic, but also carry states, which lays the foundation for subsequent learning of more complex Go language features (such as interfaces, goroutines).
7. Expand thinking
If you want to further consolidate the knowledge points of the closure and Fibonacci sequences, you can try the following expansion needs to improve the code ability:
- Limit the range of the Fibonacci number: Modify the closure, when the generated Fibonacci number exceeds the specified threshold (such as 1000), it returns -1, which means end;
- Support reset function: add a reset method to the closure, and after the call, you can start to generate the Fibonacci number (starting from 0);
- Generate a specified number of Fibonacci sequences: modify the fibonacci function, receive an int parameter n, and return a slice, including the first n Fibonacci numbers (still use closures).