First class functions in Go

前端 未结 5 1511
鱼传尺愫
鱼传尺愫 2021-01-30 08:49

I come from JavaScript which has first class function support. For example you can:

  • pass a function as a parameter to another function
  • return a function f
5条回答
  •  无人共我
    2021-01-30 08:58

    Just a brainteaser with recursive function definition for chaining middlewares in a web app.

    First, the toolbox:

    func MakeChain() (Chain, http.Handler) {
        nop := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {})
        var list []Middleware
        var final http.Handler = nop
        var f Chain
        f = func(m Middleware) Chain {
            if m != nil {
                list = append(list, m)
            } else {
                for i := len(list) - 1; i >= 0; i-- {
                    mid := list[i]
    
                    if mid == nil {
                        continue
                    }
    
                    if next := mid(final); next != nil {
                        final = next
                    } else {
                        final = nop
                    }
                }
    
                if final == nil {
                    final = nop
                }
                return nil
            }
            return f
        }
        return f, final
    }
    
    type (
        Middleware func(http.Handler) http.Handler
        Chain      func(Middleware) Chain
    )
    

    As you see type Chain is a function that returns another function of the same type Chain (How first class is that!).

    Now some tests to see it in action:

    func TestDummy(t *testing.T) {
        c, final := MakeChain()
        c(mw1(`OK!`))(mw2(t, `OK!`))(nil)
        log.Println(final)
    
        w1 := httptest.NewRecorder()
        r1, err := http.NewRequest("GET", "/api/v1", nil)
        if err != nil {
            t.Fatal(err)
        }
        final.ServeHTTP(w1, r1)
    }
    
    func mw2(t *testing.T, expectedState string) func(next http.Handler) http.Handler {
        return func(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                val := r.Context().Value(contextKey("state"))
                sval := fmt.Sprintf("%v", val)
                assert.Equal(t, sval, expectedState)
            })
        }
    }
    
    func mw1(initialState string) func(next http.Handler) http.Handler {
        return func(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                ctx := context.WithValue(r.Context(), contextKey("state"), initialState)
                next.ServeHTTP(w, r.WithContext(ctx))
            })
        }
    }
    
    type contextKey string
    

    Again, this was just a brainteaser to show we can use first class functions in Go in different ways. Personally I use chi nowadays as router and for handling middlewares.

提交回复
热议问题