前言
函數式程式設計 (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 在某些方面類似動態型別語言。