While exercising Go with various Future implementations, at one moment reflection hit me. Using reflection to inspect types beats the point of strong types, but it never hurts to play.
I want to use any function within a Future and then call it asynchronous and wait for results. I could have a function to create the Future which holds the user function. Not having generics yet, any function could be passed by interface{}, then called by reflection.
So the user function is passed to a function which returns another function that can be called with the user function arguments. The user function will be executed on a new routine.
// Callable is the function to call in order to start running the passed function. type Callable func(args ...interface{}) *Future // New returns a Callable. func New(function interface{}) Callable {
For a sum function, the usage is:
sum := func(a, b int) (int, error) { return a + b, nil } future := futurereflect.New(sum)(1, 2) result, err := future.Result()
The Future should allow waiting for the user function to be executed and get its result.
type Future struct { functionType reflect.Type functionValue reflect.Value args []interface{} wait chan struct{} result interface{} err error } // Wait blocks until function is done. func (f *Future) Wait() { <-f.wait } // Result retrieves result and error. It blocks if function is not done. func (f *Future) Result() (interface{}, error) { <-f.wait return f.result, f.err }
The rest is just implementation, but it feels like pure madness. I did it for fun, to experiment, there are cases missing. And a language should not be pushed where it doesn’t belong. Use each language in its style.
Run the code on Go Playground.
package futurereflect import ( "reflect" ) // Future represents a function which executes async work. type Future struct { functionType reflect.Type functionValue reflect.Value args []interface{} wait chan struct{} result interface{} err error } // Wait blocks until function is done. func (f *Future) Wait() { <-f.wait } // Result retrieves result and error. It blocks if function is not done. func (f *Future) Result() (interface{}, error) { <-f.wait return f.result, f.err } func (f *Future) run() { defer close(f.wait) numParams := f.functionType.NumIn() values := make([]reflect.Value, numParams) for i := 0; i < numParams; i++ { values[i] = reflect.ValueOf(f.args[i]) } ret := f.functionValue.Call(values) if len(ret) == 0 { return } f.result = ret[0].Interface() if f.functionType.NumOut() > 1 && !ret[1].IsNil() { f.err = ret[1].Interface().(error) } } // Callable is the function to call in order to start running the passed function. type Callable func(args ...interface{}) *Future // New returns a Callable. func New(function interface{}) Callable { if function == nil { return nil } functionType := reflect.TypeOf(function) if functionType.Kind() != reflect.Func { return nil } errorInterface := reflect.TypeOf((*error)(nil)).Elem() if functionType.NumOut() > 1 && !functionType.Out(1).Implements(errorInterface) { return nil } return func(args ...interface{}) *Future { future := &Future{ functionType: functionType, functionValue: reflect.ValueOf(function), args: args, wait: make(chan struct{}), } go future.run() return future } }
package futurereflect_test import ( "strings" "testing" "github.com/stretchr/testify/assert" "github.com/andreiavrammsd/workexec/futurereflect" ) type lines struct { out string } func (l *lines) str(lines []string) { l.out = strings.Join(lines, "\n") } func (l *lines) delete() { l.out = "" } func Test(t *testing.T) { sum := func(a, b int) (int, error) { return a + b, nil } future := futurereflect.New(sum)(1, 2) result, err := future.Result() assert.Equal(t, 3, result) assert.NoError(t, err) ls := &lines{} futureLines := futurereflect.New(ls.str)([]string{"A", "B"}) futureLines.Wait() assert.Equal(t, "A\nB", ls.out) futureDelete := futurereflect.New(ls.delete)() futureDelete.Wait() assert.Equal(t, "", ls.out) sumMany := func(a, b, c int) int { return a + b + c } futureSumMany := futurereflect.New(sumMany)(1, 2, 3) result, err = futureSumMany.Result() assert.Equal(t, 6, result.(int)) assert.NoError(t, err) }