[Golang] 程式設計教學:函數式程式設計 (Functional Programming)

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    函數式程式設計 (functional programming) 是另一種程式設計模範 (paradigm),其思想主要可見於 LISP 和 ML 家族的程式語言。雖然函數式語言不是主流,但函數式程式易於平行處理,近年來又逐漸抬頭。一些主流程式語言,包括 C++、Java、C# 等,都加入一些函數式程式的特性。大數據 (big data) 框架 Hadoop 和 Spark 的思想,也是基於函數式程式。Go 雖然不是函數式語言,但提供一些函數式程式的特性。

    函式物件

    函數式程式的基本前提是函式為一級物件,簡單地說,函式也是值,可以做為參數也可做為回傳值。例如,以下是合法的 Go 程式:

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        f := func() {
            fmt.Println("Hello World")
        }
     
        f()
    }
    

    在本例中,我們建立一個函式物件 f,再呼叫之。

    高階函式

    我們也可以將函式物件做為另一個函式的參數,這種函式稱為高階函式 (higher-order function)。見下例:

    package main
     
    import (
        "fmt"
    )
     
    func apply(arr []float64, callback func(float64) float64) []float64 {
        out := make([]float64, len(arr))
     
        for i, e := range arr {
            out[i] = callback(e)
        }
     
        return out
    }
     
    func main() {
        arr := []float64{1, 2, 3, 4, 5}
     
        // Square
        sqr := apply(arr, func(n float64) float64 { return n * n })
        fmt.Println(sqr)
     
        // Cubic
        cub := apply(arr, func(n float64) float64 { return n * n * n })
        fmt.Println(cub)
     
        // Inverse
        inv := apply(arr, func(n float64) float64 { return 1.0 / n })
        fmt.Println(inv)
    }
    

    在本例中,我們對同一個函式 apply 傳入不同的函式,得到不同的結果。

    閉包

    函式物件除了做為參數,也可以做為回傳值。見下例:

    package main
     
    import (
        "log"
    )
     
    func num() func() int {
        n := -1
     
        return func() int {
            n += 1
            return n
        }
    }
     
    func main() {
        f := num()
     
        if !(f() == 0) {
            log.Fatal("Wrong number")
        }
     
        if !(f() == 1) {
            log.Fatal("Wrong number")
        }
     
        if !(f() == 2) {
            log.Fatal("Wrong number")
        }
    }
    

    在本例中,每次呼叫函式 f,得到的數值會遞增 1,這種帶有狀態的函式,稱為閉包 (closure)。

    Strict Evaluation vs. Lazy Evaluation

    主流的程式語言大抵上都是 strict evaluation,例如,以下的 Go 程式是錯的:

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        fmt.Println(len([]int{1, 2, 3 / 0, 4}))
    }
    

    但以下等效的 Haskell 程式是正確的:

    main = print len
     
    list = [1, 2, 3/0, 4]
    len = length list
    

    這是由於 Haskell 有 lazy evaluation 的特性。

    純函式

    純函式 (pure function) 是函數式程式設計的一個概念,對於純函式來說,只要輸入的參數是相同的,得到的輸出就是相同的;換句話說,純函式沒有副作用 (side effect)。副作用在電腦程式中相當常見,像是改變某個物件內在狀態、將結果輸出到終端機、將資料存入外部檔案等;然而,過度依賴函式的副作用,有時候反而造成預期外的錯誤。函數式程式設計的其中一個概念就是減少副作用。Go 沒有特別強調純函式的觀念,但程式設計者可在撰寫程式時盡力避免副作用。

    遞迴

    遞迴不是函數式程式設計專有的特性,即使像是 C 這種非函數式程式語言也有遞迴的機制。然而,函數式程式語言支援 tail-call optimization,使得遞迴和控制結構達到相近的速度;Go 未支援此項特性。

    型別推論

    型別推論 (type inference) 也是函數式程式設計的其中一項特色,很多函數式程式語言都內建這項功能,像是 Haskell 或是 OCaml 等。Go 吸收這項特性,使得 Go 在某些方面類似動態型別語言。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk