函数

在Go语言中,函数是一等(first-class)公民,函数类型也是一等的数据类型。
函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等。函数值可以由此成为能够被随意传播的独立逻辑组件(或者说功能模块)。

一等公民

开头说的,函数是一等公民,函数类型是一等数据类型:

package mainimport "fmt"type calcFunc func(int, int) intfunc add(x, y int) int { return x + y}func sub(x, y int) int { return x - y}func main() { var f1, f2 calcFunc f1, f2 = add, sub fmt.Println(f1(1, 2)) fmt.Println(f2(100, 50))}

这里,先声明了一个函数类型。在下面声明的两个函数的前面与caleFunc是一致的。因此都是caleFunc的一个实现。在main函数中,分别把两个函数赋值给caleFunc类型的变量f1和f2,然后可以调用它们。
这里书写函数签名的方式与函数声明的是一致的。只是紧挨在参数列表左边的不是函数名称,而是关键字func。这里函数名称和func互换了一下位置而已。
函数签名,就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。
“函数是一等的公民”是函数式编程(functional programming)的重要特征。Go语言在语言层面支持了函数式编程。

高阶函数

高阶函数可能具有如下两个特点:

接收其他的函数作为参数传入把其他的函数作为结果返回

函数只要满足了上面的任意一个特点,就可以说这个函数是一个高阶函数。高阶函数也是函数式编程中的重要概念和特征。
在上面的例子的基础上,写一个高阶函数,然后再main主函数里调用执行:

package mainimport ( "errors" "fmt")type calcFunc func(int, int) intfunc add(x, y int) int { return x + y}func sub(x, y int) int { return x - y}func calculate(x, y int, f calcFunc) (int, error) { if f == nil { return 0, errors.New("invalid calcFunc") } return f(x, y), nil}func main() { res, err := calculate(1, 2, add) if err != nil { fmt.Println("ERROR:", err) } fmt.Println("Result:", res)}

上面的calculate就是一个高阶函数,“接收其他的函数作为参数传入”的这种高阶函数。下面的示例是另外一种高阶函数“把其他的函数作为结果返回”:

package mainimport ( "errors" "fmt")type calcFunc func(int, int) intfunc add(x, y int) int { return x + y}func sub(x, y int) int { return x - y}type resFunc func(int, int) (int, error)func genCalculator(f calcFunc) resFunc { return func(x, y int) (int, error) { if f == nil { return 0, errors.New("invalid calcFunc") } return f(x, y), nil }}func main() { f := genCalculator(sub) res, _ := f(10, 6) fmt.Println("Result:", res)}闭包

自由变量,在一个函数中存在对外来标识符的引用。所谓的外来标识符,是既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的。
上面的例子中的genCalculator函数内部,实际上就实现了一个闭包。而genCalculator函数也是一个高阶函数:

func genCalculator(f calcFunc) resFunc { return func(x, y int) (int, error) { if f == nil { return 0, errors.New("invalid calcFunc") } return f(x, y), nil }}

genCalculator函数只做了一件事,那就是定义一个匿名的函数并把它作为结果值返回。而这个匿名的函数就是一个闭包函数。它里面使用的变量f既不代表它的任何参数或结果也不是它自己声明的,而是定义它的genCalculator函数的参数,所以是一个自由变量。而自由变量具体是什么,并不是在定义闭包的时候确定的,而是在genCalculator函数被调用的时候确定的。

函数是Go语言支持函数式编程的主要体现。我们可以通过“把函数传给函数”以及“让函数返回函数”来编写高阶函数,也可以用高阶函数来实现闭包,并以此做到部分程序逻辑的动态生成。

最后,还有一个闭包的典型例子:

package mainimport "fmt"func increment () func() { var x int return func () { x ++ fmt.Println(x) }}func main() { f := increment() f() // 打印1 f() // 打印2 f() // 打印3}

主函数里,每次调用执行的结果都会变化。变量x属于闭包的一部分,但是是在闭包的函数外的,每次调用闭包后的x的状态都会保留下来。