Go Future with Reflection

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.