0%

First Class Function In Go

参考: First Class Function Blog

定义

First Class Function 是一种编程语言的术语, 指当一个函数可以赋值给变量, 可以当作参数传参, 可以作为返回值返回时, 称为First Class Function. 而Go语言拥有First Class Function特性;

解释

这一部分将结合Go语言语法与各种使用场景阐述First Class Function;

Scenario 1: Anonymous function

Anonymous function (匿名函数)指的是没有函数名称的函数, 其在项目中往往充当简单的功能函数, 其使用方式很灵活, 例如:

  • 赋值Anonymous function给某变量;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import (
    "fmt"
    )

    func main() {
    a := func() {
    fmt.Println("Hello First Class Function")
    }
    a()
    fmt.Printf("Type of variable a is %T", a)
    }

    输出为:

    1
    2
    Hello First Class Function
    Type of variable a is func()

    上面定义了一个Anonymous function,其会输出Hello First Class Function ,并赋值给a;
    随后调用a, 并打印出了a的类型, 发现其为func();

  • Anonymous function 创建的同时运行;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import (
    "fmt"
    )

    func main() {
    func() {
    fmt.Println("Hello First Class Function")
    }()
    }

    输出:

    1
    Hello First Class Function

    对于Anonymous function也可以不赋值, 例如上面定义了Anonymous function的同时立马运行, 输出了Hello First Class Function;

  • Anonymous function 也支持传参;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import (
    "fmt"
    )

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

    对于Anonymous function, 自然也可以像普通函数一样通过传参的方式使用; 例如上面定义了一个接受string入参的Anonymous function.

Scenario 2: User defined function types

Go 语言支持用户自定义函数类型, 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

type Min func(a, b int) int

func main() {
var m Min = func(a, b int) int {
if a < b {
return a
}
return b
}
res := m(5, 6)
fmt.Println("Min", res)
}

输出:

1
Min 5

上面定义了一个自定义函数类型Min, 随后通过Anonymous function的方式赋值给Min类型的变量m; 随后调用函数m获得两数中的小数;

Scenario 3: Higher-order functions

Higher-order functions 高维函数指的是符合以下两者之一的函数:

  • 函数传参: 入参中至少包含一个函数入参
  • 返回函数: 返回值中包含有函数

函数传参:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

func higher(f func(a, b int) int) {
fmt.Printf("Func result: %v", f(5, 6))
}

func main() {
min := func(a, b int) int {
if a < b {
return a
}
return b
}
higher(min)
}

输出:

1
Func result: 5

上面的例子中, 我定义了一个higher高维函数, 其接受一个函数入参f, 并在内部逻辑中输出f(5, 6)的结果; 在main函数中, 定义了一个Anonymous function 并赋值给变量min; 最后将min传递给higher函数执行.

返回函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

func higher() func(a, b int) int {
return func(a, b int) int {
if a < b {
return a
}
return b
}
}

func main() {
f := higher()
fmt.Printf("Result is: %v", f(5, 6))
}

输出为:

1
Result is: 5

在上面的例子中, 我定义了一个higher高维函数, 其会返回一个实现了Min逻辑的函数; 在main函数中, 我通过变量f接收了higher的返回值, 并在最后输出f的执行结果;

Scenario 4: Closures

Closures 闭包是anonymous functions的特殊用例, 指的是在Function Body 内使用了外部的变量, 导致外部变量在函数使用期间无法被GC回收;例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"))
}

输出为:

1
2
Hello World
Hello World Gopher

在上面的例子中appendStr函数返回了一个closure, 其绑定了变量t; 在main函数中将closure赋值给变量a, 并在随后调用了两次; 可以发现, 第二次调用时的输出为Hello World Gopher而不是Hello Gopher. 这说明变量tclosure使用过程中一直被引用, 并且其值会随着多次调用,产生无法预期的变化. 在项目中, 一般避免使用Closure, 以免产生不必要的误解;

用例

这一部分将通过一个简单的用例, 说明First Class Function的用于Dependency Injection的用例场景;

  • 定义一个student结构体:

    1
    2
    3
    4
    5
    6
    type student struct {
    firstName string
    lastName string
    grade string
    country string
    }

    student结构体中包含有学生基础信息, 姓, 名, 分数, 国家;

  • 定义一个filter函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func filter(s []student, f func(student) bool) []student {
    var r []student
    for _, v := range s {
    if f(v) == true {
    r = append(r, v)
    }
    }
    return r
    }

    定义了一个过滤函数filter, 其接受一个student列表与filter logic: f, 函数中会遍历student列表并返回符合f的学生列表;

定义了一个过滤函数filter, 其接受一个student列表与filter logic: f, 函数中会遍历student列表并返回符合f的学生列表;

  • 函数主逻辑如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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) == true {
    r = append(r, v)
    }
    }
    return r
    }

    func studentList() []student {
    s1 := student{
    firstName: "Naveen",
    lastName: "Ramanathan",
    grade: "A",
    country: "India",
    }
    s2 := student{
    firstName: "Samuel",
    lastName: "Johnson",
    grade: "B",
    country: "USA",
    }
    s3 := student{
    firstName: "Sun",
    lastName: "Qingzhi",
    grade: "C",
    country: "China",
    }
    return []student{s1, s2, s3}
    }

    func main() {
    s := studentList()
    f := filter(s, func(s student) bool {
    if s.grade == "C" {
    return true
    }
    return false
    })
    fmt.Println(f)
    }

    main函数逻辑中, 首先通过studentList函数获得了初始化学生列表, 随后通过调用filter函数, 并注入了一个匿名函数

    1
    2
    3
    4
    5
    6
    7
    // 找到成绩为C的学生
    func(s student) bool {
    if s.grade == "C" {
    return true
    }
    return false
    }

    随后获得了符合匿名函数的学生列表;

    输出为:

    1
    [{Sun Qingzhi C China}]

    这么做的好处在于将filter logicCaller拆分开来, 是一种松耦合的编码方式;

    如果发现目前有业务调整, 需要得到来自于India的学生列表, 那么只需要将filter logic逻辑更换为:

    1
    2
    3
    4
    5
    6
    7
    // 找到来自于印度的学生
    func(s student) bool {
    if s.country == "India" {
    return true
    }
    return false
    }

    输出为:

    1
    [{Naveen Ramanathan A India}]

    总结

本文介绍的核心为First Class Function, 分别介绍了定义, Go中的使用场景与用例实战. 相信认真读到这里, 可以发现First Class Function是一个简单的术语, 在项目中或多或少的都有涉及. 期望这一篇文章可以对你有所帮助;