[譯] part 33: golang 頭等函式

咔嘰咔嘰發表於2019-04-14

什麼是頭等函式

支援頭等函式的語言允許將函式賦值給變數,作為引數傳遞給其他函式並從其他函式返回。 Go 支援頭等函式功能。

在本教程中,我們將討論頭等函式的語法和各種用例。

匿名函式

讓我們從一個簡單的例子開始,它將一個函式賦給變數。

package main

import (  
    "fmt"
)

func main() {  
    a := func() {
        fmt.Println("hello world first class function")
    }
    a()
    fmt.Printf("%T", a)
}
複製程式碼

Run in playground

在上面的程式中的第 8 行,我們為變數a分配了一個函式。 這是將函式賦值給變數的語法。如果你仔細觀察的話會發現,分配給a的函式沒有名稱。這些沒有名稱的函式稱為匿名函式。

呼叫此函式的唯一方法是使用變數a。我們使用 a()呼叫該函式,將列印hello world first class function。在第 12 行,我們列印變數a的型別,將列印func()

執行該程式,將輸出,

hello world first class function  
func()  
複製程式碼

也可以不賦值給變數來呼叫匿名函式,讓我們看看以下示例中怎麼完成的此操作。

package main

import (  
    "fmt"
)

func main() {  
    func() {
        fmt.Println("hello world first class function")
    }()
}
複製程式碼

Run in playground

在上面的程式中,我們在第 8 行定義了一個匿名函式。 在函式定義之後,我們在行號中使用()呼叫函式。 該程式將輸出,

hello world first class function  
複製程式碼

也可以像任何其他函式一樣將引數傳遞給匿名函式。

package main

import (  
    "fmt"
)

func main() {  
    func(n string) {
        fmt.Println("Welcome", n)
    }("Gophers")
}
複製程式碼

Run in playground

在上面的程式中的第 10 行,將字串引數傳遞給匿名函式。執行此程式將列印,

Welcome Gophers  
複製程式碼

使用者定義的函式型別

就像定義自己的結構型別一樣,我們也可以定義自己的函式型別。

type add func(a int, b int) int  
複製程式碼

上面的程式碼片段建立了一個新的函式型別add,它接受兩個整數引數並返回一個整數。現在我們可以定義add型別的變數。

讓我們編寫一個定義add型別變數的程式。

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)
}
複製程式碼

Run in playground

在上面的程式的第 10 行,我們定義了一個型別為add的變數a,併為其賦予與add型別匹配的函式。在第 13 行,我們呼叫該函式,並將結果分配給s。該程式將列印,

Sum 11  
複製程式碼

高階函式

wiki上高階函式的定義是滿足以下至少一個條件

  • 將一個或多個函式作為引數
  • 返回一個函式作為結果

讓我們看一下上述兩種情況的一些簡單示例。

將函式作為引數傳遞給其他函式
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)
}
複製程式碼

Run in playground

在上面的例子中的第 7 行,我們定義一個函式simple,它接受一個函式,該函式接受兩個int引數並返回一個int作為引數。在 main 函式裡面,我們建立了一個匿名函式f,其與函式simple的引數匹配。我們呼叫simple,並在下一行中將f作為引數傳遞給它。該程式列印67作為輸出。

從其他函式返回函式

現在讓我們重寫上面的程式並從simple函式返回一個函式。

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))
}
複製程式碼

Run in playground

在上面程式中的第 7 行,simple函式返回一個函式,該函式接受兩個int引數並返回一個int引數。

在第 15 行呼叫了simple函式,然後將返回值分配給s。現在s包含simple函式返回的函式。我們呼叫s並傳遞兩個int引數。 該程式將輸出67

閉包

閉包是匿名函式的特例。閉包是匿名函式,用於訪問該匿名函式體外定義的變數。

用個例子瞭解一下它,

package main

import (  
    "fmt"
)

func main() {  
    a := 5
    func() {
        fmt.Println("a =", a)
    }()
}
複製程式碼

Run in playground

在上面程式中的第 10 行,匿名函式訪問函式體外定義的變數a。因此,這個匿名函式是一個閉包。

每個閉包都把變數繫結到它周圍的變數。讓我們通過一個簡單的例子來理解這是什麼意思。

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()
    b := appendStr()
    fmt.Println(a("World"))
    fmt.Println(b("Everyone"))

    fmt.Println(a("Gopher"))
    fmt.Println(b("!"))
}
複製程式碼

Run in playground

在上面的程式中,函式appendStr返回一個閉包。該閉包繫結到變數t

變數ab分別在第 17 和 18 行中宣告為閉包,它們與自己的t值繫結。

我們先用引數World呼叫a。現在,a閉包t的值變成了Hello World

在第 20 行,我們用引數Everyone呼叫b。由於b繫結到自己的變數t,因此b閉包的初始值為Hello。在此函式呼叫之後,b閉包變數t的值將變為Hello Everyone

程式將輸出,

Hello World  
Hello Everyone  
Hello World Gopher  
Hello Everyone ! 
複製程式碼

頭等函式的實際用法

到目前為止,我們已經定義了頭等函式,並且我們已經編寫了一些例子來了解它們的工作原理。現在讓我們編寫一個具體的程式,它揭示了頭等函式的實際用法。

我們將建立一個程式,根據某些標準過濾一部分學生。讓我們一步一步地解決這個問題。

首先定義學生型別,

type student struct {  
    firstName string
    lastName string
    grade string
    country string
}
複製程式碼

下一步是編寫filter函式。該函式的入參為student結構的切片和一個確認student是否符合條件的函式。編寫這個函式,能讓我們更好地理解。讓我們繼續吧。

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
}
複製程式碼

在上面的函式中,第二個引數是一個以student為引數並返回bool的函式。此函式是為了確認該student是否符合條件。我們在第 3 行中迭代student切片,然後將每個student作為引數傳遞給函式f。如果返回true,則表示student符合過濾條件,並將其新增到結果切片r中。你可能對此功能的實際使用感到有些困惑,繼續完成該程式就會更清楚。我新增了 main 函式,並在下面提供了完整的程式。

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 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 {
        if s.grade == "B" {
            return true
        }
        return false
    })
    fmt.Println(f)
}
複製程式碼

Run in playground

在 main 函式中,我們首先建立兩個student型別的變數s1s2,並將它們新增到切片s中作為引數傳遞給filter函式。現在我想找出所有B級的學生。我們在上面的程式中通過一個函式來檢查學生是否有B級,如果是,那麼返回true。上述程式將列印,

[{Samuel Johnson B USA}]
複製程式碼

假設我們想找到所有來自印度的學生。將filter函式引數改變一下,可以輕鬆完成此操作。

我提供了以下程式碼,

c := filter(s, func(s student) bool {  
    if s.country == "India" {
        return true
    }
    return false
})
fmt.Println(c)  
複製程式碼

請將其新增到 main 函式並檢查輸出。

讓我們再寫一個程式來結束本節。該程式將對切片的每個元素執行相同的操作並返回結果。例如,如果我們想要將切片中的所有整數乘以 5 並返回輸出,則可以使用頭等函式輕鬆完成。這些對集合的每個元素進行操作的函式稱為map函式(譯者注:map/reduce 函式)。我提供了以下程式。

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)
}
複製程式碼

上述程式將輸出,

[25 30 35 40 45]
複製程式碼

快速回顧一下本教程的內容,

  • 什麼是頭等函式
  • 匿名函式
  • 使用者定義的函式型別
  • 高階函式
    • 將函式作為引數傳遞給其他函式
    • 從其他函式返回函式
  • 閉包
  • 頭等函式的實際用法

相關文章