# Go语言的闭包

首先我们要知道什么是函数式编程。

## 函数式编程特点

#### **函数是一等公民**

所谓["第一等公民"](http://en.wikipedia.org/wiki/First-class_function)（first class），指的是函数与其他数据类型一样，处于平等地位，**可以赋值给其他变量**，**也可以作为参数**，**传入另一个函数**，或者作为别的函数的返回值。

#### 只用表达式，不用语句

"表达式"（expression）是一个单纯的运算过程，总是有返回值；每一步都是单纯的运算，所以**不能使用if,for之类的语句。**

#### 没有“副作用”

函数式编程强调没有"副作用"，意味着函数要保持独立，所有功能就是返回一个新的值，没有其他行为，尤其是不得修改外部变量的值。

#### 不修改状态

变量往往用来保存"状态"（state）。不修改变量，意味着状态不能保存在变量中。函数式编程使用参数保存状态，最好的例子就是递归。

#### 引用透明

引用透明（Referential transparency），指的是函数的运行不依赖于外部变量或"状态"，只依赖于输入的参数，任何时候只要参数相同，引用函数所得到的返回值总是相同的。

## 例子

```go
func adder() func(int) int {
	sum := 0
	return func(v int) int {
		sum += v
		return sum
	}
}

func main() {
	// a := adder() is trivial and also works.
	b := adder()
	for i := 0; i < 10; i++ {
		fmt.Printf("0 + 1 + ... + %d = %d\n",
			i, b(i))
	}
```

adder返回一个函数func，在内部定义return的里面返回了另外一个函数，这个函数里通过v对sum进行了追加。sum则保存了上一次函数调用时的值。

```
0 + 1 + ... + 0 = 0
0 + 1 + ... + 1 = 1
0 + 1 + ... + 2 = 3
0 + 1 + ... + 3 = 6
0 + 1 + ... + 4 = 10
0 + 1 + ... + 5 = 15
0 + 1 + ... + 6 = 21
0 + 1 + ... + 7 = 28
0 + 1 + ... + 8 = 36
0 + 1 + ... + 9 = 45
```

在这个程序里面，v是这个函数的局部变量，sum则是自由变量， 当函数返回的时候不是返回函数，而是这个函数的闭包。

![](/files/-LgH1eeU78a0Oi4Ut095)

但是函数式编程规定不能有状态（sum），所以之前的函数还需要修改一下。

```go
type iAdder func(int) (int, iAdder)

func adder2(base int) iAdder {
	return func(v int) (int, iAdder) {
		return base + v, adder2(base + v)
	}
}
	a := adder2(0)
	for i := 0; i < 10; i++ {
		var s int
		s, a = a(i)
		fmt.Printf("0 + 1 + ... + %d = %d\n",
			i, s)
	}
```

在执行完第8行的时候，a其实是相当于：

```go
return func(v int) (int, iAdder) {
		return base + v, adder2(base + v)
	}
```

**在第8行的时候上面的最内层的return语句并没有执行**，而在进入for之后，每次都会执行上面的返回函数，同时更新base的值，由于base属于a的闭包，所以base的值会不断被累加。

### 斐波那契数列

#### 实现1

```go
package fib

// 1, 1, 2, 3, 5, 8, 13, ...
func Fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}
```

这样我们每调用一次函数，他就会执行一次。

```go
func main() {
	f := fibFibonacci()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}
```

结果

```
1
1
2
3
5
8
13
21
34
```

#### 实现2

我们也可以运用Interface的知识，把他写成一个接口。

```go
type intGen func() int

func (g intGen) Read(
	p []byte) (n int, err error) {
	next := g()
	if next > 10000 {
		return 0, io.EOF
	}
	s := fmt.Sprintf("%d\n", next)

	// TODO: incorrect if p is too small!
	return strings.NewReader(s).Read(p)
}

func printFileContents(reader io.Reader) {
	scanner := bufio.NewScanner(reader)

	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

func main() {
	var f intGen = fib.Fibonacci()
	printFileContents(f)
}
```

注意intGen虽然是个函数，但是仍然可以定义方法，同时可以实现接口。

由于intGen实现了read方法，所以intGen也是一个Reader。可以把它传进printFileContents里，同时每次scanner.Scan()的时候都会调用read方法。

#### 实现3

虽然有点脱线了，但是还可以看看递归的实现。

```go
package main

import (
	"fmt"
)

func fibonacci(num int) int{
	if num<2{
		return 1
	}
	return fibonacci(num-1) + fibonacci(num-2)
}
 
 
func main(){
	for i := 0; i<10; i++{
		nums := fibonacci(i)
		fmt.Println(nums)
	}
}
```

递归的解说个人觉得[这个知乎回答](https://www.zhihu.com/question/31412436/answer/683820765)比较好理解


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dingyj.gitbook.io/blog/golang/basic/go-yu-yan-xue-xi-bi-ji/go-yu-yan-de-bi-bao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
