Home > front end >  Passing an arbitrary function as a parameter in Go
Passing an arbitrary function as a parameter in Go

Time:10-29

I'm trying to expand my knowledge of Go's function pointers, and I have a question about what is and is not possible with passing functions as parameters in Go.

Let's say that I want to write a decorator() function that can wrap any existing function. For simplicity, let's limit this to functions that accept exactly one parameter and return exactly one value.

If I write a decorator that accepts func(interface{}) interface{} as it's argument, it will implicitly work as long as that function I pass in also accepts/returns an interface{} type (see funcA).

My question is--is there a way to convert an existing function of type func(string) string to a type of func(interface{}) interface{} so that it can also be passed into a decorator function without just wrapping it in a new anonymous function (see funcB)?

package main

import (
    "fmt"
)

func decorate(inner func(interface{}) interface{}, args interface{}) interface {} {
    fmt.Println("Before inner")
    result := inner(args)
    fmt.Println("After inner")
    return result
}

func funcA(arg interface{}) interface{} {
    fmt.Print("Inside A, with arg: ")
    fmt.Println(arg)
    return "This is A's return value"
}

func funcB(arg string) string {
    fmt.Print("Inside B, with arg: ")
    fmt.Println(arg)
    return "This is B's return value"
}

func main() {
    
    // This one works. Output is:
    //
    //   Before inner
    //   Inside A, with arg: (This is A's argument)
    //   After inner
    //   This is A's return value
    //
    fmt.Println(decorate(funcA, "(This is A's argument)"))
    
    // This doesn't work. But can it?
    //fmt.Println(decorate(funcB, "(This is B's argument)"))
}

CodePudding user response:

This is not possible. One reason for that is the mechanics of passing parameters differ from function to function, and using an interface{} arg does not mean "accept anything". For example, a function taking a struct as an arg will receive each member of that struct, but a function taking an interface{} containing that struct will receive two words, one containing the type of the struct, and the other containing a pointer to it.

So, without using generics, the only way to implement this is by using an adapter function.

CodePudding user response:

Use the reflect package to handle functions with arbitrary argument and result types.

func decorate(inner interface{}, args interface{}) interface{} {
    fmt.Println("Before inner")
    result := reflect.ValueOf(inner).Call([]reflect.Value{reflect.ValueOf(args)})
    fmt.Println("After inner")
    return result[0].Interface()
}

Run the code on the playground.

Like the decorate function in the question, the function in this answer assumes one argument and one result. The function must be modified to handle other function types.

The OP should consider the tradeoffs between the anonymous wrapper function proposed in the question and the use of the reflect package here. Calling the function through the reflect API is slower than calling the function through the anonymous wrapper. There's also a loss of type safety with the reflect API. The anonymous wrapper function adds verbosity.

CodePudding user response:

is there a way to convert an existing function of type func(string) string to a type of func(interface{}) interface{} so that it can also be passed into a decorator function without just wrapping it in a new anonymous function (see funcB)?

No. It's that simple: No.

  • Related