# 一等公民函数

#### 函数是一等公民

Golang中函数可以：

* 赋值给某个变量
* 作为入参传递给另外一个函数
* 作为其他函数的返回值

在编程语言中，我们一般说这种函数是类似于变量的一等公民，因为在其他编程语言中，一般只有变量才符合这些特性。

当然，除了Golang，Javascript中函数也是一等公民。将函数做为一等公民非常有意义，这类编程语言一般可以做为函数式编程语言使用。

#### 匿名函数

我们先来看一个将函数赋值给变量的例子。

```go
package main

import "fmt"

func main() {
    a := func() {
        fmt.Println("hello world first class function")
    }
    a()
    fmt.Printf("%T", a)
}
```

这里我们将一个函数赋值给了变量`a`。注意这个函数没有声明函数名称，这类函数一般称为`匿名函数`。

由于该`匿名函数`没有函数名称，因此我们只能通过变量`a`来调用。该程序执行结果如下：

```
hello world first class function
func()
```

当然我们也可以通过`立即调用`来调用匿名函数。

```go
package main

import "fmt"

func main() {
    func() {
        fmt.Println("hello world first class function")
    }()
}
```

这里我们定义了一个匿名函数并进行了立即调用，直接结果如下：

```
hello world first class function
```

立即调用也可以传参数进去。

```go
package main

import "fmt"

func main() {
    func(n string) {
        fmt.Println("Welcome", n)
    }("Gophers")
}
```

该程序执行结果如下：

```
Welcome Gophers
```

#### 自定义函数类型

我们不仅可以定义结构体类型、接口类型，还可以自定义函数类型。

```go
type add func(a int, b int) int
```

这行代码定义了一个新的函数类型`add`，该类型的函数要求接收两个`int`参数作为入参，函数返回值类型也为`int`。下面我们定义一个该类型函数的变量。

```go
package main

import "fmt"

type add func(a int, b int) int

func main() {
    var a add = func(a int, b int) int {
        return a + b
    }
    s := a(5, 6)
    fmt.Println("Sum", s)
}
```

这里我们定义了一个`add`类型的变量`a`，我们给该变量赋的值是一个入参为两个`int`，返回值也为`int`的函数。注意我们赋值的函数签名必须符合`add`类型的定义。该程序执行结果如下：

```
Sum 11
```

#### 高阶函数

**符合下面两个条件之一的函数我们称为高阶函数：**

* **存在一个或多个函数作为入参**
* **将一个新的函数作为返回值**

我们分别就这两种情况举个例子。

**将函数作为入参传给其他函数**

```go
package main

import "fmt"

func simple(a func(a, b int) int) {
    fmt.Println(a(60, 7))
}

func main() {
    f := func(a, b int) int {
        return a + b
    }
    simple(f)
}
```

这里我们定义了函数`simple`，该函数的入参是函数类型。我们在`main`函数内部创建了一个符合`simple`入参要求的匿名函数，并传递给`simple`函数来调用。程序输出为`67`。

**返回一个新函数**

```go
package main

import "fmt"

func simple() func(a, b int) int {
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {
    s := simple()
    fmt.Println(s(60, 7))
}
```

这里定义的函数`simple`返回了一个新函数`f`，`f`入参为两个`int`，返回值类型也是`int`。我们在`main`函数中调用`simple()`，赋值给`s`。因此`s`的值即为函数`f`。程序输出为`67`。

#### 闭包

**将函数作为一等公民的编程语言一般都有`闭包`的概念，比如Javascript，Golang也不例外。`闭包`是指在函数内部可以访问到函数外部定义的变量。当然，如果函数嵌套级别较深，可能会有多层访问，也就形成了作用域链。** 听起来有点晦涩，我们通过例子来理解下。

```go
package main

import "fmt"

func appendStr() func(string) string {
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {
    a := appendStr()
    fmt.Println(a("World"))
    fmt.Println(a("Gopher"))
}
```

我们定义了一个函数`appendStr`，该函数的返回值是一个新函数，因此该函数是一个高阶函数。在`appendStr`函数内部，我们定义了变量`t`，因此在被返回的函数`c`内部我们是能够访问到`t`这个变量的，也就是说`t`在函数`c`的**作用域链**上。

我们在`main`函数中调用`appendStr()`赋值给变量`a`，这时`a`的值即为`c`，是`appendStr`返回的函数。由于`t`变量在函数`c`的作用域链上，因此每次调用`a`(也就是调用`c`)时都能访问到变量`t`，也就是说可以多次调用`a`来更新变量`t`的值。因此第一次调用`a`时`t`由`Hello`更新成了`Hello World`，第二次调用`a`时，`t`的值已经时`Hello World`了。

该程序输出如下：

```
Hello World
Hello World Gopher
```

**这里提到的作用域链非常重要，原文没有提到这个，我个人觉得重要就补充了下，是学习高阶函数和闭包不容易理解的地方，是难点也是核心。**

下面我们升级下前面的程序，来深刻体会下作用域链。

```go
package main

import "fmt"

var g = "Hi"

func appendStr() func(string) string {
    t := "Hello"
    g = g + " " + "Hi"
    c := func(b string) string {
        t = t + " " + b + g
        return t
    }
    return c
}

func main() {
    a := appendStr()
    b := appendStr()
    fmt.Println(a("World"))
    fmt.Println(b("Everyone"))
    fmt.Println(a("Gopher"))
    fmt.Println(b("!"))
}
```

这里我们在函数`appendStr`外部定义了一个变量`g`，因此`g`位于函数`appendStr`的作用域链上。调用`appendStr`可以访问到其作用域链上的`g`，第一次调用`appendStr`后`g`更新为`Hi Hi`，第二次调用后`g`更新为`Hi Hi Hi`。该程序输出为：

```
Hello WorldHi Hi Hi
Hello EveryoneHi Hi Hi
Hello WorldHi Hi Hi GopherHi Hi Hi
Hello EveryoneHi Hi Hi !Hi Hi Hi
```

#### 真实适用场景

前面我们看了几个例子，下面我们看下作为一等公民的函数都有哪些真实适用场景。 我来实现实现一个函数`filter`来根据某些条件过滤一个切片，这在函数式编程中是非常常见的一种用法。

首先我们定义几个结构体类型`student`。

```go
type student struct {
    firstName string
    lastName string
    grade string
    country string
}
```

下面实现函数`filter`，入参为一个切片列表，一个过滤条件判断函数。

```go
func filter(s []student, f func(student) bool) []student {
    var s []student
    for _, v := range s {
        if f(v) {
            r = append(r, v)   
        }
    }
}
```

下面是完成程序：

```go
package main

import "fmt"

type student struct {
    firstName string
    lastName  string
    grade     string
    country   string
}

func filter(s []student, f func(student) bool) []student {
    var r []student
    for _, v := range s {
        if f(v) {
            r = append(r, v)
        }
    }
    return r
}

func main() {
    s1 := student{
        firstName: "Naveen",
        lastName:  "Ramanathan",
        grade:     "A",
        country:   "India",
    }
    s2 := student{
        firstName: "Samuel",
        lastName:  "Johnson",
        grade:     "B",
        country:   "USA",
    }
    s := []student{s1, s2}
    f := filter(s, func(s student) bool {
        return s.grade == "B"
    })
    fmt.Println(f)
}
```

我们定义了两个`student`结构体变量，然后通过`filter`函数过滤出了`grade`为`B`的student。输出为：`[{Samuel Johnson B USA}]`。

假设我们现在像过滤出来自`India`，即印度的同学，那么只需要把过滤判断函数改为如下即可：

```go
f := filter(s, func(s student) bool {
    return s.country == "India"
})
fmt.Println(f)
```

是不是很方便好用？

下面我们再实现一个函数`iMap`，用来遍历处理一个`int`类型的切片，并返回处理后的切片。

```go
package main

import "fmt"

func iMap(s []int, f func(int) int) []int {
    var r []int
    for _, v := range s {
        r = append(r, f(v))
    }
    return r
}
func main() {
    a := []int{5, 6, 7, 8, 9}
    r := iMap(a, func(n int) int {
        return n * 5
    })
    fmt.Println(r)
}
```

这里我们将切片中的每个元素都乘了5，输出为：`[25 30 35 40 45]`。

**像`filter`、`map`这些函数都是借助高阶函数这个特性来实现的，其实本质上是属于函数式编程的范畴，感兴趣的话可以接着去深入学习函数式编程，Haskell、Golang、Javascript、Python等编程语言都支持函数式编程。学习函数式编程对编程水平的提升绝对大有裨益。**
