Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go type for function call

Tags:

go

Keywords like go and defer expect a function call as parameters. Is there a type available that can be used the same way? (e.g. to write a function that expects a function call - opposed to a function - as argument).

like image 379
vbence Avatar asked Jul 03 '20 13:07

vbence


1 Answers

No there is not. You can't do the same with your function.

go and defer are backed by the language spec and the rule is enforced by the compiler.

What you may do is use a variable / value of function type, which you may call later / at any time as if it would be a function.

For example:

func myFunc() {
    fmt.Println("hi")
}

func main() {
    var f func()
    f = myFunc
    f() // This calls the function value stored in f: myFunc in this example
}

Edit: To have the functionality you mentioned in the comment: just wrap the function call with its arguments in a func(), and use / pass that.

For example:

func launch(f func()) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        f()
    }()
}

Using it:

func main() {
    launch(func() {
        fmt.Println("Hello, playground")
    })

    time.Sleep(time.Second)
}

Which outputs (try it on the Go Playground):

Before launch
Hello, playground
After completion

Yes, this is not an exact workaround. If the params may change, you have to make a copy of them before calling launch(), and use the copy in the function literal (closure), like in this example:

s := "Hello, playground"

s2 := s // make a copy
launch(func() {
    fmt.Println(s2) // Use the copy
})
s = "changed"

Mimicing automatic parameter saving

For a concrete function type we may construct a helper function which provides us automatic parameter saving. This helper function must have identical signature, and return a function without parameters. The returned function is a closure which calls the original function with the parameters. The act of calling this helper function is the mechanism to save the parameters, so the usage is identical to that of defer.

For example the helper for fmt.Println(s) is:

func wrapPrintln(s string) func() {
    return func() {
        fmt.Println(s)
    }
}

And using it:

launch(wrapPrintln(s))

Example for a function with 2 int parameters:

func Sum(a, b int) {
    fmt.Println("Sum:", a+b)
}

func WrapSum(a, b int) func() {
    return func() {
        Sum(a, b)
    }
}

launch(WrapSum(a, b))

The above WrapPrintln() and WrapSum() wrapped a concrete function, and it can't be used for other functions (the wrapped function is "wired in"). We can make the wrapped functions a parameter too:

func WrapFuncIntInt(f func(a, b int), a, b int) func() {
    return func() {
        f(a, b)
    }
}

And we may use it like this:

launch(WrapFuncIntInt(Sum, a, b))

Try this one on the Go Playground.

Using reflection to avoid the manual copies

You may use reflection to avoid having to make manual copies, but in this solution we're not actually calling the function, just passing it. Also due to using reflection, it will be slower. Another advantage is that this "feels" generic (we may use functions with different signatures), but we lose compile-time safety.

func launch(f interface{}, params ...interface{}) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        pv := make([]reflect.Value, len(params))
        for i, v := range params {
            pv[i] = reflect.ValueOf(v)
        }
        reflect.ValueOf(f).Call(pv)
    }()
}

Example calling it:

func main() {
    i, s := 1, "Hello, playground"

    launch(fmt.Printf, "%d %q\n", i, s)
    i, s = 2, "changed"

    time.Sleep(time.Second)
}

Which outputs (try it on the Go Playground):

Before launch
1 "Hello, playground"
After completion

Single exception where you can utilize automatic parameter saving

There is a single exception which we may use. This is the Method value. If x has static type T and T's method set contains the method M, we may use x.M (without calling it).

The expression x.M is a method value, and it saves a copy of x which will be used as the receiver when the expression's result (which is a function value) is called.

Example:

type myParams struct {
    format string
    i int
    s string
}

func (mp myParams) Call() {
    fmt.Printf(mp.format, mp.i, mp.s)
}

func main() {
    p := myParams{format: "%d %q\n", i: 1, s: "Hello, playground"}
    launch(p.Call) // p is saved here
    p.i, p.s = 2, "changed"

    time.Sleep(time.Second)
}

func launch(f func()) {
    fmt.Println("Before launch")
    go func() {
        defer fmt.Println("After completion")
        f()
    }()
}

It outputs the same. Try it on the Go Playground.

like image 165
icza Avatar answered Oct 23 '22 05:10

icza