Alexander Ponomarev
Alexander Ponomarev

Reputation: 2728

Wrapper for arbitrary function in Go

Is it possible to create a wrapper for arbitrary function in Go that would take the same arguments and return the same value?

I'm not talking about the wrapper that would look exactly the same, it may look differently, but it should solve the problem.

For example the problem might be to create a wrapper of arbitrary function that first looks for the result of the function call in cache and only in case of cache miss executes the wrapped function.

Upvotes: 10

Views: 8126

Answers (5)

WeSt
WeSt

Reputation: 2684

Building on previous answers and using Go's new generic capabilities, I believe this can be implemented quite elegantly (playground link):

package main

import (
    "fmt"
    "reflect"
)

// Creates wrapper function and sets it to the passed pointer to function
func wrapFunction[T any](function T) T {
    v := reflect.MakeFunc(reflect.TypeOf(function), func(in []reflect.Value) []reflect.Value {
        // This is the place to intercept your call.
        fmt.Println("Params are:", in)

        f := reflect.ValueOf(function)
        return f.Call(in)
    })
    return v.Interface().(T)
}

func main() {
    // The function being wrapped itself
    sum := func(a int, b int) int {
        return a + b
    }
    wrapped := wrapFunction(sum)

    fmt.Printf("Result is %v", wrapped(1, 3))
}

Upvotes: 5

PuppyKhan
PuppyKhan

Reputation: 130

The best I've come up with is to take a function def and return an interface, which will need type assertion afterwards:

func Wrapper(metaParams string, f func() (interface{}, string, error)) (interface{}, error) {
    // your wrapper code
    res, metaResults, err := f()
    // your wrapper code
    return res, err
}

Then to use this also takes a little work to function like a wrapper:

resInterface, err := Wrapper("data for wrapper", func() (interface{}, string, error) {
    res, err := YourActualFuntion(whatever, params, needed)
    metaResults := "more data for wrapper"
    return res, metaResults, err
}) // note f() is not called here! Pass the func, not its results
if err != nil {
    // handle it
}
res, ok := resInterface.(actualType)
if !ok {
    // handle it
}

The upside is this is somewhat generic, can handle anything with 1 return type + error, and doesn't require reflection.

The downside is this takes a lot of work to use as it's not a simple wrapper or decorator.

Upvotes: 2

Alexander Ponomarev
Alexander Ponomarev

Reputation: 2728

The answer based on @joshlf13 idea and answer, but seems more simple to me. http://play.golang.org/p/v3zdMGfKy9

package main

import (
    "fmt"
    "reflect"
)

type (
    // Type of function being wrapped
    sumFuncT func(int, int) (int)

    // Type of the wrapper function
    wrappedSumFuncT func(sumFuncT, int, int) (int)
)

// Wrapper of any function
// First element of array is the function being wrapped
// Other elements are arguments to the function
func genericWrapper(in []reflect.Value) []reflect.Value {
    // this is the place to do something useful in the wrapper
    return in[0].Call(in[1:])
}

// Creates wrapper function and sets it to the passed pointer to function
func createWrapperFunction(function interface {}) {
    fn := reflect.ValueOf(function).Elem()
    v := reflect.MakeFunc(reflect.TypeOf(function).Elem(), genericWrapper)
    fn.Set(v)
}

func main() {
    var wrappedSumFunc wrappedSumFuncT

    createWrapperFunction(&wrappedSumFunc)

    // The function being wrapped itself
    sumFunc := func (a int, b int) int {
        return a + b
    }

    result := wrappedSumFunc(sumFunc, 1, 3)
    fmt.Printf("Result is %v", result)
}

Upvotes: 4

joshlf
joshlf

Reputation: 23597

Here's a solution using reflect.MakeFunc. This particular solution assumes that your transformation function knows what to do with every different type of function. Watch this in action: http://play.golang.org/p/7ZM4Hlcqjr

package main

import (
    "fmt"
    "reflect"
)

type genericFunction func(args []reflect.Value) (results []reflect.Value)

// A transformation takes a function f,
// and returns a genericFunction which should do whatever
// (ie, cache, call f directly, etc)
type transformation func(f interface{}) genericFunction

// Given a transformation, makeTransformation returns
// a function which you can apply directly to your target
// function, and it will return the transformed function
// (although in interface form, so you'll have to make
// a type assertion).
func makeTransformation(t transformation) func(interface{}) interface{} {
    return func(f interface{}) interface{} {
        // g is the genericFunction that transformation
        // produced. It will work fine, except that it
        // takes reflect.Value arguments and returns
        // reflect.Value return values, which is cumbersome.
        // Thus, we do some reflection magic to turn it
        // into a fully-fledged function with the proper
        // type signature.
        g := t(f)

        // typ is the type of f, and so it will also
        // be the type that of the function that we
        // create from the transformation (that is,
        // it's essentially also the type of g, except
        // that g technically takes reflect.Value
        // arguments, so we need to do the magic described
        // in the comment above).
        typ := reflect.TypeOf(f)

        // v now represents the actual function we want,
        // except that it's stored in a reflect.Value,
        // so we need to get it out as an interface value.
        v := reflect.MakeFunc(typ, g)
        return v.Interface()
    }
}

func main() {
    mult := func(i int) int { return i * 2 }

    timesTwo := func(f interface{}) genericFunction {
        return func(args []reflect.Value) (results []reflect.Value) {
            // We know we'll be getting an int as the only argument,
            // so this type assertion will always succeed.
            arg := args[0].Interface().(int)

            ff := f.(func(int) int)

            result := ff(arg * 2)
            return []reflect.Value{reflect.ValueOf(result)}
        }
    }

    trans := makeTransformation(timesTwo)

    // Since mult multiplies its argument by 2,
    // and timesTwo transforms functions to multiply
    // their arguments by 2, f will multiply its
    // arguments by 4.
    f := trans(mult).(func(int) int)

    fmt.Println(f(1))
}

Upvotes: 3

OneOfOne
OneOfOne

Reputation: 99332

Like this?

var cache = make(map[string]string)

func doStuff(key string) {
   //do-something-that-takes-a-long-time
   cache[key] = value

   return value
}

fun DoStuff(key string) {
   if v, ok := cache[key]; ok {
        return v
   }

   return doStuff(key)
}

Upvotes: 0

Related Questions