Skip to content

Instantly share code, notes, and snippets.

@telyn
Forked from anonymous/chain.go
Last active April 15, 2024 06:42
Show Gist options
  • Save telyn/7ee88a52a68367075e6dff1f1bdf1af8 to your computer and use it in GitHub Desktop.
Save telyn/7ee88a52a68367075e6dff1f1bdf1af8 to your computer and use it in GitHub Desktop.
Function chaining in go. It's essentially the same as the Either Monad - but a little less monad-y
package chain
import (
"fmt"
"reflect"
)
// Chain takes an array of functions, and calls them all, passing the result of the previous call to the next. Any funcs whose last return value is an error have the errors checked and not passed to the next. If an error occurs at any point chain returns with an empty list and the error that happened.
// here it is expressed vaguely maths-y which might be clearer.
// given a function f[n] which returns a set of values vs:
// if the last element of vs is of a type which implements the error type,
// the last element is removed from vs and stored as err.
// if err is not nil, return an empty slice of values and err.
// otherwise, call f[n+1](vs)
func Chain(funcs ...interface{}) (res []interface{}, err error) {
data := []reflect.Value{}
for _, f := range funcs {
val := reflect.ValueOf(f)
typ := reflect.TypeOf(f)
if typ.Kind() != reflect.Func {
data = []reflect.Value{}
err = fmt.Errorf("one of the funcs wasn't a func.")
return
}
data = val.Call(data)
if len(data) > 0 {
errVal := data[len(data)-1]
if errVal.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) {
errI := errVal.Interface()
if errI != nil {
err = errI.(error)
return
}
data = data[:len(data)-1]
}
}
}
res = make([]interface{}, len(data))
for i, d := range data {
res[i] = d.Interface()
}
return
}
package chain_test
import (
"fmt"
"math"
"strconv"
chain "."
)
func ExampleChain() {
res, err := chain.Chain(
func() (string, int) { return "400.2", 32 },
strconv.ParseFloat,
math.Ceil,
func(f float64) (string, float64) { return "%0.f", f },
fmt.Sprintf,
)
if err != nil {
fmt.Println("there was an error!")
}
fmt.Println(res[0])
// Output: 401
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment