Go Senior Engineer Explains (MOOC) 006_Functional Programming

Go Functional Programming

Corresponds to video Ch6 (6-2 Functional Programming Example One), expanding on 002.md with more functional programming patterns

1. Review: Functions are First-Class Citizens in Go

Go is not a purely functional language, but functions can be used as:
- Variables
- Parameters
- Return values
- Stored in data structures

// 函数作为变量
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 In-Depth

Closure = Function + its referenced external variables (free variables). The function and the variables it references together form a closure.

2.1 Accumulator (already in 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 Fibonacci Generator (already in 002.md)

func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

2.3 Closure for Iterator Pattern

// 通用的整数序列迭代器
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 Closure Pitfall: Loop Variable Capture

// 错误示范
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. Higher-Order Function Patterns

3.1 Map / Filter / Reduce

The Go standard library does not have generic versions of map/filter/reduce (Go 1.18+ allows implementing them with generics).

// 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 Function Composition (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. Decorator / Middleware Pattern

This is one of the most common practical scenarios for functional programming in Go, widely used in HTTP middleware.

4.1 Basic Decorator

// 给任意函数添加执行时间日志
func timeTrack(fn func()) func() {
    return func() {
        start := time.Now()
        fn()
        fmt.Printf("Execution time: %v\n", time.Since(start))
    }
}

func main() {
    slowFunc := func() {
        time.Sleep(100 * time.Millisecond)
        fmt.Println("done")
    }
    tracked := timeTrack(slowFunc)
    tracked()
    // done
    // Execution time: 100.xxxms
}

4.2 HTTP Middleware Chain (Most common in real projects)

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 Comparing with errWrapper in 002.md

The errWrapper in 002.md is essentially a decorator pattern:

// 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 Pattern

This is one of the most popular functional design patterns in the Go community, proposed by Dave Cheney and 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)
}

Advantages:
- No need to remember parameter order
- Adding new options does not break existing code
- Clear default values
- More Go-idiomatic than the builder pattern

6. Functional Programming for Tree Traversal (002.md Extension)

TraverseFunc is already in 002.md; here are more applications:

// 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. Lazy Evaluation and Infinite Sequences

Simulating lazy evaluation using channels + goroutines:

// 无限自然数序列
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. Functional Error Handling

An approach to avoid numerous if err != nil checks:

// 链式操作,遇到错误就停止
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
        })
}

Note: This pattern is not recommended for extensive use in production Go code, as it sacrifices type safety and readability. The Go community prefers the explicit if err != nil style. This is merely a demonstration of functional programming concepts.

9. Summary

Pattern Application Scenarios in Go Commonness
Closure Lazy evaluation, state encapsulation, generators Very Common
Higher-Order Functions sort.Slice, http.HandleFunc Very Common
Decorator/Middleware HTTP middleware, logging, authentication, rate limiting Very Common
Functional Options Library/framework configuration API Very Common
Map/Filter/Reduce Data processing (more practical with Go 1.18+ generics) Common
Function Composition Pipeline processing Occasional
Lazy Evaluation Simulated with channel + goroutine Occasional
Functional Error Handling Experimental, not recommended for production use Rare

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6748

(0)
Walker的头像Walker
上一篇 13 hours ago
下一篇 Apr 20, 2025 19:12

Related Posts

EN
简体中文 繁體中文 English