一级函数


作为一门现代语言,Go 的特色之一是函数为一级函数(First-class function),可以作为值来进行传递。

函数作为值

例如你定义一个取最大值的函数max,你可以将此函数作为值传递给maximum

package main

import "fmt"

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    maximum := max
    fmt.Println(max(10, 5))     // 10
    fmt.Println(maximum(10, 5)) // 10
}

可以看到,被max参考的函数,也被maximum参考着,因而,现在透过max或者maximum,都可以调用函数。

因为 Go 类型推断能力的关系,上头的maximum并不用定义类型,而可以直接参考max函数的类型,那么,max或者是maximum的类型是什么呢?

package main

import "fmt"
import "reflect"

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    maximum := max
    fmt.Println(reflect.TypeOf(max))     // func(int, int) int
    fmt.Println(reflect.TypeOf(maximum)) // func(int, int) int
}

可以看到,函数的类型包括了func、参数类型与返回值类型,但不用定义函数、参数与返回值的名称。

定义函数变量

你可以仅定义一个变量可用来参考特定类型的函数,例如:

package main

import "fmt"

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    var maximum func(int, int) int
    fmt.Println(maximum) // nil

    maximum = max
    fmt.Println(maximum(10, 5)) // 10
}

若想先定义一个maximum变量,可以在之后参考max函数,可以使用类型func(int, int) int来定义,通常,定义函数变量时,若想免于冗长的函数类型定义,可以使用type来定义一个新的类型名称:

package main

import "fmt"

type BiFunc func(int, int) int // 定义了新类型

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    var maximum BiFunc
    fmt.Println(maximum) // nil

    maximum = max
    fmt.Println(maximum(10, 5)) // 10
}

在上例中,BiFunc是个新的定义类型(defined type),底层类型(underlying type)为func(int, int) int,Go 会认定两者属于不同类型,因为新的类型会拥有新的名称,在 Go 1.9 前,这是避免冗长函数类型定义的唯一方式。

不过,就这边来说,实际上只是想要func(int, int) int能有个简短一点的名称,从 Go 1.9 开始,可以为类型取别名,别名就只是同一类型的另一个名称,:

package main

import "fmt"

type BiFunc = func(int, int) int // 类类型名定义

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    var maximum BiFunc
    fmt.Println(maximum) // nil

    maximum = max
    fmt.Println(maximum(10, 5)) // 10
}

在这边,BiFunc只是func(int, int) int的另一个名称,而不是新的类型。

函数变量既然是个变量,也就可以对它取指针,例如:

package main

import "fmt"

type BiFunc = func(int, int) int

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    var maximum BiFunc
    fmt.Println(&maximum) // 0x1040a130
    // fmt.Println(&max)
}

如上,你可以对maximum取指针,得到变量地址,不过,你不能对定义的max取指针,去除程序中最后一个注解的话,会发生 cannot take the address of max 的错误。

回调应用

因为函数可以当作值传递,因此,对于函数中流程几乎相同,只有少数操作不同的情况,就可以将操作不同的部份以回调(Callback)函数取代。例如,可以设计一个filter函数,用来过滤出符合特定条件的值:

package main

import "fmt"

type Predicate = func(int) bool

func filter(origin []int, predicate Predicate) []int {
    filtered := []int{}
    for _, elem := range origin {
        if predicate(elem) {
            filtered = append(filtered, elem)
        }
    }
    return filtered
}

func greaterThan7(n int) bool {
    return n > 7
}

func lessThan5(n int) bool {
    return n < 5
}

func main() {
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(filter(data, greaterThan7))
    fmt.Println(filter(data, lessThan5))    
}

在这个例子中,filter函数重用了for rangeif等流程,只要传入过滤用的函数,就可以让filter具有各种的过滤用途。

除了作为值传递之外,Go 的函数还可以是匿名函数,且具有闭包(Closure)的特性,这将在下一篇文件加以说明。


展开阅读全文