Wrapper for arbitrary function in Go
Asked Answered
R

5

10

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.

Ruby answered 19/4, 2014 at 5:48 Comment(5)
Do you mean a function which, when given another function, returns a third function with the same signature as the first function? So, for example, do you want something like func transform(f func(blah) blah) func(blah) blah where you could pass in func f(int) bool like this: transform(f) and have it return another function whose signature was also func(int) bool?Ligialignaloes
I think that in theory you can write a function which accepts another function (as a value) and an arbitrary number of arguments of type interface{}. It would then use reflect to get the types of arguments and return value(s) of the function to be wrapped, and construct a call to it, then execute that call. I'm not sure reflect has all the tools to do that so I'm not posting this as an answer. But to be honest your question sounds like a case of the XY problem, so it would be better if you would narrow your problem statement down.Ultimogeniture
@joshlf13 yes, I meant what you described, but if this is not possible, the other solution would be suitable, the main goal is to pass the same arguments in some way and get the same result in some way. Maybe it will not be exactly func(int) bool that will be returned, may be some other interface, but the goal is to pass int and get bool, not matter what interface will be.Ruby
@kostix, I think the solution is just about reflection in Go, it seems like there are no other ways. Who knows though.Ruby
No, it is possible. It will just be very difficult. You can do it using the reflect package's MakeFunc. Almost everything in the reflect package is subtle to begin with, and of those, MakeFunc is one of the worst. I think I can make something work, but it will not be pretty or easy to understand.Ligialignaloes
R
4

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)
}
Ruby answered 19/4, 2014 at 9:39 Comment(0)
O
5

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))
}
Otology answered 23/2, 2022 at 11:13 Comment(0)
R
4

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)
}
Ruby answered 19/4, 2014 at 9:39 Comment(0)
L
3

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))
}
Ligialignaloes answered 19/4, 2014 at 8:43 Comment(3)
I should note, however, that it would be a horrible idea to use this in production, and I'd think long and hard about whether there's some other design decision that you can make that doesn't necessitate this kind of witchcraft.Ligialignaloes
Thank you for the idea, it was what I needed, however I added my own answer based on yours, which seems simpler. People will be able to look at both and pick easier for them to understand.Ruby
Yeah, yours is simpler. Although it does restrict you to functions which take two ints and return one int (not sure if that's an issue).Ligialignaloes
W
2

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.

Whitson answered 6/7, 2017 at 21:48 Comment(0)
C
0

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)
}
Carlile answered 19/4, 2014 at 5:56 Comment(4)
No, I mean arbitrary function. The goal is not to write a wrapper for every function you need to wrap, but write it once and use for every function you need.Ruby
Wrong language for the taskCarlile
I don't have a task to wrap function, the task is to build system and wrapper is a helper for building the system. If I would have the task to wrap function I would choose other language. I mean the system is already written in Go and I can't rewrite it.Ruby
@AlexanderPonomarev Go has few utilities to work with dynamic types. Especially, you cannot expect the compiler to resolve any dynamic stuff at compile time. Another problem is that the reflection model of Go does not allow you to look into fields of objects that are private. As a logical consequence, reflect does not provide a way to generically hash or order structs of non-static type, one of which would be needed to implement memoization efficiently. Go is not the right tool for dynamic wrapper trickery.Jackiejackinoffice

© 2022 - 2024 — McMap. All rights reserved.