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)
}