Last active
October 30, 2017 12:39
-
-
Save donutloop/33aa05733191c5f86856534719d9d784 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The Package reflect in the Go standard library implements run-time reflection, allowing a program to manipulate objects with arbitrary types. | |
We can build with the reflection package a kind of generic functionality. Because this package give us the possibility to determine the meta information of a function and it's input and output arguments while runtime. | |
Be careful when you start to use the reflection package it's hard to debug run time errors. For more information about reflection, I can recommend the following article about reflection. | |
See "The Laws of Reflection" for an introduction to reflection in Go: https://golang.org/doc/articles/laws_of_reflection.html | |
If you want see reflection in action below is a example: | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package concurrent | |
import ( | |
"sync" | |
"reflect" | |
) | |
// Run executes the provided functions in concurrent and collects any errors they return. | |
func ReflectRun(fns ...[]interface{}) []error { | |
wg := sync.WaitGroup{} | |
errCh := make(chan error, len(fns)) | |
wg.Add(len(fns)) | |
for _, fn := range fns { | |
if len(fn) <= 1 { | |
panic("Must be a pair of func and input values") | |
} | |
fnType := reflect.TypeOf(fn[0]) | |
// panic if conditions not met (because it's a programming error to have that happen) | |
switch { | |
case fnType.Kind() != reflect.Func: | |
panic("value must be a function") | |
case fnType.NumOut() != 1: | |
panic("func must have exactly one output argument") | |
} | |
// the first output parameter must be a error | |
outType := fnType.Out(0) | |
if ok := outType.Implements(reflect.TypeOf((*error)(nil)).Elem()); !ok { | |
panic("func output argument must be a error") | |
} | |
inputValues := []reflect.Value{} | |
for i, valRaw := range fn[1:] { | |
if fnType.In(i).Kind() != reflect.TypeOf(reflect.ValueOf(valRaw).Interface()).Kind(){ | |
panic("func input value is bad") | |
} | |
inputValues = append(inputValues, reflect.ValueOf(valRaw)) | |
} | |
go func(fn interface{}, inputValues []reflect.Value) { | |
outputValues := reflect.ValueOf(fn).Call(inputValues) | |
if err, ok := outputValues[0].Interface().(error); ok && err != nil { | |
errCh <- err | |
} | |
wg.Done() | |
}(fn[0], inputValues) | |
} | |
wg.Wait() | |
close(errCh) | |
var errs []error | |
for err := range errCh { | |
errs = append(errs, err) | |
} | |
return errs | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package concurrent | |
import ( | |
"fmt" | |
"sync/atomic" | |
"testing" | |
) | |
func TestReflectRun(t *testing.T) { | |
i := int32(0) | |
fn1 := []interface{}{ | |
func(ii int32) error { | |
i = atomic.AddInt32(&ii, 3) | |
return nil | |
}, | |
int32(3), | |
} | |
fn2 := []interface{}{ | |
func(ii int32) error { | |
i = atomic.AddInt32(&ii, 3) | |
return nil | |
}, | |
int32(3), | |
} | |
errs := ReflectRun( | |
fn2, | |
fn1, | |
) | |
if len(errs) != 0 || i != 6 { | |
t.Errorf("unexpected run") | |
} | |
testErr := fmt.Errorf("an error") | |
y := int32(0) | |
fn3 := []interface{}{ | |
func(yy int32) error { | |
return testErr | |
}, | |
int32(3), | |
} | |
fn4 := []interface{}{ | |
func(yy int32) error { | |
y = atomic.AddInt32(&yy, 3) | |
return nil | |
}, | |
int32(3), | |
} | |
errs = ReflectRun( | |
fn3, | |
fn4, | |
) | |
if len(errs) != 1 && errs[0] != testErr && y != 3 { | |
t.Error("unexpected run") | |
} | |
x := 0 | |
fn5 := []interface{}{ | |
func(xx int) error { | |
xxx := int32(xx) | |
x = int(atomic.AddInt32(&xxx, 3)) | |
return nil | |
}, | |
3, | |
} | |
fn6 := []interface{}{ | |
func(xx int) error { | |
xxx := int32(xx) | |
x = int(atomic.AddInt32(&xxx, 3)) | |
return nil | |
}, | |
3, | |
} | |
errs = ReflectRun( | |
fn5, | |
fn6, | |
) | |
if len(errs) != 0 || x != 6 { | |
t.Errorf("unexpected run ") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment