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

Golang函式
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 在某些方面類似動態型別語言。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。