Go 函数式编程
对应视频 Ch6(6-2 函数式编程例一),在 002.md 基础上扩展更多函数式编程模式
1. 回顾:Go 中函数是一等公民
Go 不是纯函数式语言,但函数可以作为:
- 变量
- 参数
- 返回值
- 存放在数据结构中
// 函数作为变量
var add = func(a, b int) int { return a + b }
// 函数作为参数
func apply(op func(int, int) int, a, b int) int {
return op(a, b)
}
// 函数作为返回值
func makeGreeter(greeting string) func(string) string {
return func(name string) string {
return greeting + ", " + name + "!"
}
}
func main() {
fmt.Println(apply(add, 3, 4)) // 7
hello := makeGreeter("Hello")
fmt.Println(hello("Go")) // Hello, Go!
}
2. 闭包(Closure)深入
闭包 = 函数 + 它引用的外部变量(自由变量)。函数和它引用的变量共同构成闭包。
2.1 累加器(002.md 已有)
func adder() func(int) int {
sum := 0 // 自由变量,被闭包捕获
return func(v int) int {
sum += v
return sum
}
}
// a := adder()
// a(1) -> 1, a(2) -> 3, a(10) -> 13
2.2 斐波那契生成器(002.md 已有)
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
2.3 闭包实现迭代器模式
// 通用的整数序列迭代器
func intRange(start, end int) func() (int, bool) {
current := start
return func() (int, bool) {
if current > end {
return 0, false
}
val := current
current++
return val, true
}
}
func main() {
next := intRange(1, 5)
for v, ok := next(); ok; v, ok = next() {
fmt.Println(v) // 1 2 3 4 5
}
}
2.4 闭包的陷阱:循环变量捕获
// 错误示范
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // 捕获的是变量 i 的地址,不是值
})
}
for _, f := range funcs {
f() // 输出 3 3 3,不是 0 1 2
}
}
// 正确做法:通过参数传值
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
i := i // 重新声明局部变量(Go 的惯用写法)
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f() // 输出 0 1 2
}
}
3. 高阶函数模式
3.1 Map / Filter / Reduce
Go 标准库没有泛型版本的 map/filter/reduce(Go 1.18+ 可以用泛型自己实现)。
// Go 1.18+ 泛型实现
func Map[T, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
func Filter[T any](s []T, f func(T) bool) []T {
var result []T
for _, v := range s {
if f(v) {
result = append(result, v)
}
}
return result
}
func Reduce[T, U any](s []T, init U, f func(U, T) U) U {
result := init
for _, v := range s {
result = f(result, v)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5}
// 每个元素 *2
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6 8 10]
// 过滤偶数
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4]
// 求和
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println(sum) // 15
}
3.2 函数组合(Compose)
// 将两个函数组合成一个
func Compose[T any](f, g func(T) T) func(T) T {
return func(x T) T {
return f(g(x))
}
}
func main() {
double := func(x int) int { return x * 2 }
addOne := func(x int) int { return x + 1 }
doubleAndAddOne := Compose(addOne, double)
fmt.Println(doubleAndAddOne(3)) // (3*2)+1 = 7
}
4. 装饰器 / 中间件模式
这是 Go 中函数式编程最常用的实际场景,广泛应用于 HTTP 中间件。
4.1 基本装饰器
// 给任意函数添加执行时间日志
func timeTrack(fn func()) func() {
return func() {
start := time.Now()
fn()
fmt.Printf("执行耗时: %v\n", time.Since(start))
}
}
func main() {
slowFunc := func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("done")
}
tracked := timeTrack(slowFunc)
tracked()
// done
// 执行耗时: 100.xxxms
}
4.2 HTTP 中间件链(实际项目最常见)
type Middleware func(http.HandlerFunc) http.HandlerFunc
// 日志中间件
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next(w, r)
}
}
// 认证中间件
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
// 链式组合中间件
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
f = middlewares[i](f)
}
return f
}
func main() {
hello := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello!"))
}
// 请求流经: Logging → Auth → hello
http.HandleFunc("/api", Chain(hello, LoggingMiddleware, AuthMiddleware))
http.ListenAndServe(":8080", nil)
}
4.3 对比 002.md 中的 errWrapper
002.md 中的 errWrapper 本质就是装饰器模式:
// 002.md 中的模式
type appHandler func(http.ResponseWriter, *http.Request) error
func errWrapper(handler appHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 统一错误处理逻辑
if err := handler(w, r); err != nil {
// 处理各种错误...
}
}
}
5. 选项模式(Functional Options)
这是 Go 社区最流行的函数式设计模式之一,由 Dave Cheney 和 Rob Pike 提出。
type Server struct {
host string
port int
timeout time.Duration
maxConn int
}
// Option 是一个函数类型
type Option func(*Server)
// 每个选项返回一个 Option 函数
func WithHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}
func WithMaxConn(maxConn int) Option {
return func(s *Server) {
s.maxConn = maxConn
}
}
// 构造函数接收可变数量的 Option
func NewServer(opts ...Option) *Server {
// 默认值
s := &Server{
host: "localhost",
port: 8080,
timeout: 30 * time.Second,
maxConn: 100,
}
// 应用所有选项
for _, opt := range opts {
opt(s)
}
return s
}
func main() {
// 灵活创建,只传需要的选项
s1 := NewServer()
s2 := NewServer(WithPort(9090), WithTimeout(60*time.Second))
s3 := NewServer(WithHost("0.0.0.0"), WithPort(443), WithMaxConn(1000))
fmt.Println(s1, s2, s3)
}
优势:
- 不需要记参数顺序
- 新增选项不破坏已有代码
- 有清晰的默认值
- 比 builder 模式更 Go 风格
6. 函数式编程用于树遍历(002.md 扩展)
002.md 中已有 TraverseFunc,这里展示更多应用:
// 002.md 中的基础
func (node *Node) TraverseFunc(f func(*Node)) {
if node == nil { return }
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
// 应用1:统计节点数
func (node *Node) Count() int {
count := 0
node.TraverseFunc(func(n *Node) {
count++
})
return count
}
// 应用2:求最大值
func (node *Node) Max() int {
maxVal := math.MinInt64
node.TraverseFunc(func(n *Node) {
if n.Value > maxVal {
maxVal = n.Value
}
})
return maxVal
}
// 应用3:收集所有值
func (node *Node) Collect() []int {
var result []int
node.TraverseFunc(func(n *Node) {
result = append(result, n.Value)
})
return result
}
7. 惰性求值与无限序列
利用 channel + goroutine 模拟惰性求值:
// 无限自然数序列
func naturals() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
// take: 从 channel 中取前 n 个值
func take(n int, ch <-chan int) []int {
result := make([]int, n)
for i := 0; i < n; i++ {
result[i] = <-ch
}
return result
}
// 筛法求素数(经典的 CSP 并发+函数式示例)
func sieve() <-chan int {
out := make(chan int)
go func() {
ch := naturals()
<-ch // 跳过 0
<-ch // 跳过 1
for {
prime := <-ch
out <- prime
// 创建新的过滤器 goroutine
next := make(chan int)
go func(src <-chan int, p int) {
for v := range src {
if v%p != 0 {
next <- v
}
}
}(ch, prime)
ch = next
}
}()
return out
}
func main() {
// 取前 20 个素数
primes := take(20, sieve())
fmt.Println(primes)
// [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71]
}
8. 函数式错误处理
避免大量 if err != nil 的一种思路:
// 链式操作,遇到错误就停止
type Result struct {
val interface{}
err error
}
func (r Result) Then(f func(interface{}) (interface{}, error)) Result {
if r.err != nil {
return r // 已经有错误,直接透传
}
val, err := f(r.val)
return Result{val, err}
}
// 使用示例
func processFile(path string) Result {
return Result{path, nil}.
Then(func(v interface{}) (interface{}, error) {
return os.Open(v.(string))
}).
Then(func(v interface{}) (interface{}, error) {
return io.ReadAll(v.(*os.File))
}).
Then(func(v interface{}) (interface{}, error) {
data := v.([]byte)
return strings.ToUpper(string(data)), nil
})
}
注意:这种模式在 Go 中不推荐在生产代码中大量使用,因为它牺牲了类型安全和可读性。Go 社区更推崇显式的
if err != nil风格。这里仅作为函数式思想的演示。
9. 总结
| 模式 | Go 中的应用场景 | 常见程度 |
|---|---|---|
| 闭包 | 延迟计算、状态封装、生成器 | 非常常见 |
| 高阶函数 | sort.Slice、http.HandleFunc | 非常常见 |
| 装饰器/中间件 | HTTP中间件、日志、认证、限流 | 非常常见 |
| Functional Options | 库/框架的配置 API | 非常常见 |
| Map/Filter/Reduce | 数据处理(Go 1.18+泛型后更实用) | 常见 |
| 函数组合 | 管道处理 | 偶尔 |
| 惰性求值 | channel + goroutine 模拟 | 偶尔 |
| 函数式错误处理 | 实验性,不推荐生产使用 | 少见 |
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6748