跳过正文
  1. 文章/

Go的函数式编程核心思想

·1493 字·3 分钟
目录

Go语言虽然不是纯粹的函数式语言,但支持函数式编程的核心思想.以下是Go语言支持函数式编程的核心思想和示例。

核心思想
#

1.函数是一等公民(First-class Functions)
#

函数可以作为参数、返回值、赋值给变量

// 函数作为参数
func process(numbers []int, fn func(int) int) []int {
    result := make([]int, len(numbers))
    for i, v := range numbers {
        result[i] = fn(v)
    }
    return result
}

// 函数作为返回值
func multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

2.不可变性(Immutability)
#

// 不修改原数据,返回新数据
func SortImmutable(arr []int) []int {
    result := make([]int, len(arr))
    copy(result, arr)
    sort.Ints(result)  // 只对新副本排序
    return result
}

3.纯函数(Pure Functions)
#

跟不可变性类似,纯函数不修改原数据,返回新数据

// 无副作用,相同输入→相同输出
func PureAdd(a, b int) int {
    return a + b  // 不依赖外部状态,不修改外部状态
}

// 不纯的例子
var counter = 0
func ImpureAdd(a, b int) int {
    counter++  // 副作用!
    return a + b
}

4.高阶函数(Higher-Order Functions)
#

高阶函数是函数式编程的核心思想,它接收函数作为参数,或者返回函数作为结果

// 接收函数作为参数
func ProcessNumbers(nums []int, transform func(int) int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = transform(n)
    }
    return result
}

// 返回函数
func Multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

5.函数组合与链式调用(Function Composition & Chaining)
#

// Builder模式的链式调用
type Query struct {
    sql string
}

func (q Query) Select(fields string) Query {
    q.sql = "SELECT " + fields
    return q
}

func (q Query) Where(cond string) Query {
    q.sql += " WHERE " + cond
    return q
}

// 链式调用
query := Query{}.Select("name, age").Where("age > 18")

示例
#

package main

import (
    "fmt"
    "strings"
)

// 3.纯函数
func toUpper(s string) string {
    return strings.ToUpper(s)
}

func addExclamation(s string) string {
    return s + "!"
}

// 4.高阶函数
func MapString(arr []string, fn func(string) string) []string {
    result := make([]string, len(arr))
    for i, v := range arr {
        result[i] = fn(v)
    }
    return result
}

func FilterString(arr []string, fn func(string) bool) []string {
    var result []string
    for _, v := range arr {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// 5.链式调用(函数组合)
func ProcessStrings(strings []string) []string {
    // 2.不可变性:每个操作都返回新切片
    return MapString(
        FilterString(
            MapString(strings, toUpper),  // 转大写
            func(s string) bool { return len(s) > 3 },  // 过滤长度>3
        ),
        addExclamation,  // 加感叹号
    )
}

func main() {
    data := []string{"go", "rust", "java", "c"}
    
    // 1.函数是一等公民
    processors := []func([]string) []string{
        ProcessStrings,
        func(arr []string) []string {
            return MapString(arr, strings.ToLower)
        },
    }
    
    for _, processor := range processors {
        result := processor(data)
        fmt.Println(result)
    }
    
    // 2.原数据保持不变(不可变性)
    fmt.Println("Original:", data)  // [go rust java c]
}

日常实践
#

函数选项设计模式
#

该设计模式在实战中十分实用,尤其适用于需要传入大量可选参数的初始化场景。它巧妙地融合了函数式编程的多个核心思想:

  1. 函数是一等公民 - 函数可以作为参数传递和返回值
  2. 纯函数 - 相同输入产生相同输出,无副作用
  3. 不可变性 - 通过配置而非修改来构建对象
  4. 高阶函数 - 函数接收函数作为参数,返回新的配置函数

具体实现细节和完整示例可以参考 函数选项设计模式 这篇文章。

编码指导
#

纯函数和不可变形
#

在日常开发中,经常遇到这样的困惑:要不要在函数里直接修改传入的参数?这时最理想的情况就需要用函数式编程的思想来指导。

举个例子:有个 check 函数用于校验接口参数,但同时又需要修改参数字段或添加新字段。

按照纯函数不可变性的原则,check 只应该做校验,不应该有副作用。正确的做法是:

  • 校验归校验check 函数只负责检查参数是否合法
  • 修改归修改:把修改参数的逻辑提取为独立函数
  • 职责清晰:每个函数只做一件事

这样代码逻辑更清晰,也更容易测试和维护。

也有两种情况,可以考虑不遵从这两个原则

// 情况1:性能敏感,且明确是"in-place"操作
// 必须有清晰的命名约定,从命名上明确告知会修改原数组
func SortInPlace(arr []int) {
    // 排序必须修改原数组
    sort.Ints(arr)  // 但函数名明确说明了"InPlace"
}

// 情况2:Builder模式
type Builder struct {
    data []int
}

func (b *Builder) Append(val int) *Builder {
    b.data = append(b.data, val)
    return b
}

✅ 函数式思维:默认返回新数据,保持输入不变

⚠️ 工程权衡:性能敏感时,可以in-place操作但需明确告知

🎯 核心原则:使代码行为可预测,减少bug

除非有充分理由(性能、API要求等),否则函数不应该修改其输入参数。这是编写可维护、可测试、并发安全代码的关键。