First class functions in Go

前端 未结 5 1504
鱼传尺愫
鱼传尺愫 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.

    0 讨论(0)
  • 2021-01-30 09:08

    The related section from the specification: Function types.

    All other answers here first declare a new type, which is good (practice) and makes your code easier to read, but know that this is not a requirement.

    You can work with function values without declaring a new type for them, as seen in the below example.

    Declaring a variable of function type which has 2 parameters of type float64 and has one return value of type float64 looks like this:

    // Create a var of the mentioned function type:
    var f func(float64, float64) float64
    

    Let's write a function which returns an adder function. This adder function should take 2 parameters of type float64 and should returns the sum of those 2 numbers when called:

    func CreateAdder() func(float64, float64) float64 {
        return func(x, y float64) float64 {
            return x + y
        }
    }
    

    Let's write a function which has 3 parameters, first 2 being of type float64, and the 3rd being a function value, a function that takes 2 input parameters of type float64 and produces a value of float64 type. And the function we're writing will call the function value that is passed to it as parameter, and using the first 2 float64 values as arguments for the function value, and returns the result that the passed function value returns:

    func Execute(a, b float64, op func(float64, float64) float64) float64 {
        return op(a, b)
    }
    

    Let's see our previous examples in action:

    var adder func(float64, float64) float64 = CreateAdder()
    result := Execute(1.5, 2.5, adder)
    fmt.Println(result) // Prints 4
    

    Note that of course you can use the Short variable declaration when creating adder:

    adder := CreateAdder() // adder is of type: func(float64, float64) float64
    

    Try these examples on the Go Playground.

    Using an existing function

    Of course if you already have a function declared in a package with the same function type, you can use that too.

    For example the math.Mod() has the same function type:

    func Mod(x, y float64) float64
    

    So you can pass this value to our Execute() function:

    fmt.Println(Execute(12, 10, math.Mod)) // Prints 2
    

    Prints 2 because 12 mod 10 = 2. Note that the name of an existing function acts as a function value.

    Try it on the Go Playground.

    Note:

    Note that the parameter names are not part of the type, the type of 2 functions having the same parameter and result types is identical regardless of the names of the parameters. But know that within a list of parameters or results, the names must either all be present or all be absent.

    So for example you can also write:

    func CreateAdder() func(P float64, Q float64) float64 {
        return func(x, y float64) float64 {
            return x + y
        }
    }
    

    Or:

    var adder func(x1, x2 float64) float64 = CreateAdder()
    
    0 讨论(0)
  • 2021-01-30 09:11
    package main
    
    import (
        "fmt"
    )
    
    type Lx func(int) int
    
    func cmb(f, g Lx) Lx {
        return func(x int) int {
            return g(f(x))
        }
    }
    
    func inc(x int) int {
        return x + 1
    }
    
    func sum(x int) int {
        result := 0
    
        for i := 0; i < x; i++ {
            result += i
        }
    
        return result
    }
    
    func main() {
        n := 666
    
        fmt.Println(cmb(inc, sum)(n))
        fmt.Println(n * (n + 1) / 2)
    }
    

    output:

    222111
    222111
    
    0 讨论(0)
  • 2021-01-30 09:12

    Go Language and Functional Programming might help. From this blog post:

    package main
    import fmt "fmt"
    type Stringy func() string
    func foo() string{
            return "Stringy function"
    }
    func takesAFunction(foo Stringy){
        fmt.Printf("takesAFunction: %v\n", foo())
    }
    func returnsAFunction()Stringy{
        return func()string{
            fmt.Printf("Inner stringy function\n");
            return "bar" // have to return a string to be stringy
        }
    }
    func main(){
        takesAFunction(foo);
        var f Stringy = returnsAFunction();
        f();
        var baz Stringy = func()string{
            return "anonymous stringy\n"
        };
        fmt.Printf(baz());
    }
    

    Author is the blog owner: Dethe Elza (not me)

    0 讨论(0)
  • 2021-01-30 09:19

    While you can use a var or declare a type, you don't need to. You can do this quite simply:

    package main
    
    import "fmt"
    
    var count int
    
    func increment(i int) int {
        return i + 1
    }
    
    func decrement(i int) int {
        return i - 1
    }
    
    func execute(f func(int) int) int {
        return f(count)
    }
    
    func main() {
        count = 2
        count = execute(increment)
        fmt.Println(count)
        count = execute(decrement)
        fmt.Println(count)
    }
    
    //The output is:
    3
    2
    
    0 讨论(0)
提交回复
热议问题