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]
}
日常实践#
函数选项设计模式#
该设计模式在实战中十分实用,尤其适用于需要传入大量可选参数的初始化场景。它巧妙地融合了函数式编程的多个核心思想:
- 函数是一等公民 - 函数可以作为参数传递和返回值
- 纯函数 - 相同输入产生相同输出,无副作用
- 不可变性 - 通过配置而非修改来构建对象
- 高阶函数 - 函数接收函数作为参数,返回新的配置函数
具体实现细节和完整示例可以参考 函数选项设计模式 这篇文章。
编码指导#
纯函数和不可变形#
在日常开发中,经常遇到这样的困惑:要不要在函数里直接修改传入的参数?这时最理想的情况就需要用函数式编程的思想来指导。
举个例子:有个 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要求等),否则函数不应该修改其输入参数。这是编写可维护、可测试、并发安全代码的关键。