一級函式


作為一門現代語言,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)的特性,這將在下一篇文件加以說明。