Go资深工程师讲解(慕课) 006_函数式编程

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

(0)
Walker的头像Walker
上一篇 14小时前
下一篇 2025年4月20日 19:12

相关推荐

  • Go工程师体系课 003

    grpc grpc grpc-go grpc 无缝集成了 protobuf protobuf 习惯用 Json、XML 数据存储格式的你们,相信大多都没听过 Protocol Buffer。 Protocol Buffer 其实是 Google 出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多! protobuf…

    后端开发 14小时前
    500
  • Go工程师体系课 010

    es 安装 elasticsearch(理解为库) kibana(理解为连接工具)es 和 kibana(5601) 的版本要保持一致 MySQL 对照学习 Elasticsearch(ES) 术语对照 MySQL Elasticsearch database index(索引) table type(7.x 起固定为 _doc,8.x 彻底移除多 type…

    后端开发 7小时前
    200
  • Go资深工程师讲解(慕课) 003

    003 测试 吐槽别人家的,go语言采用表格驱动测试 测试数据和测试逻辑混在一些 出错信息不明确 一旦一个数据出错测试全部结束 表格驱动测试 test:=[]struct{ a,b,c int32 }{ {1,2,3}, {0,2,0}, {0,0,0}, {0,0,0}, {-1,1,0}, {math.MaxInt32,1,math.MinInt32},…

  • Go工程师体系课 protoc-gen-validate

    protoc-gen-validate 简介与使用指南 ✅ 什么是 protoc-gen-validate protoc-gen-validate(简称 PGV)是一个 Protocol Buffers 插件,用于在生成的 Go 代码中添加结构体字段的验证逻辑。 它通过在 .proto 文件中添加 validate 规则,自动为每个字段生成验证代码,避免你手…

  • Go工程师体系课 014

    rocketmq 快速入门 去我们的各种配置(podman)看是怎么安装的 概念介绍 RocketMQ 是阿里开源、Apache 顶级项目的分布式消息中间件,核心组件: NameServer:服务发现与路由 Broker:消息存储、投递、拉取 Producer:消息生产者(发送消息) Consumer:消息消费者(订阅并消费消息) Topic/Tag:主题/…

简体中文 繁体中文 English