This project is inspired by samber/lo (A Lodash-style Go library based on Go 1.18+ Generics)
Why this name?
Just followed the naming convention of samber/lo with the f prefix, it stands for function.
go get github.com/nekomeowww/fo@v1
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
You can import fo
using:
import (
"github.com/nekomeowww/fo"
)
Then use one of the helpers below:
name := fo.May(func () (string, error) {
return "John", nil
})
fmt.Println(name)
// John
Most of the time, the compiler will be able to infer the type so that you can call: fo.May(...)
.
GoDoc: https://godoc.org/github.com/nekomeowww/fo
Global setters:
Function helpers:
- Invoke
- Invoke0 -> Invoke6
- InvokeWith
- InvokeWith0 -> InvokeWith6
- InvokeWithTimeout
- InvokeWithTimeout0 -> InvokeWithTimeout6
Error handling:
Sets the logger for the package.
fo.SetLogger(logrus.New())
Sets the handlers for the package.
fo.SetHandlers(
func (err error, v ...any) {
fmt.Println(err, v...)
},
func (err error, v ...any) {
fmt.Println(err, v...)
},
)
Calls any functions with context.Context
control supported and returns the result.
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
val, err := fo.Invoke(ctx, func() (string, error) {
time.Sleep(2 * time.Second)
return "John", nil
})
// val == ""
// err == context deadline exceeded
Invoke* has the same behavior as Invoke, but returns multiple values.
func example0() (error) {}
func example1() (int, error) {}
func example2() (int, string, error) {}
func example3() (int, string, time.Date, error) {}
func example4() (int, string, time.Date, bool, error) {}
func example5() (int, string, time.Date, bool, float64, error) {}
func example6() (int, string, time.Date, bool, float64, byte, error) {}
ctx1, cancel1 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel1()
err1 := fo.Invoke0(ctx1, example0())
ctx2, cancel2 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel2()
val1, err1 := fo.Invoke1(ctx1, example1()) // alias to Invoke
ctx3, cancel3 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel3()
val1, val2, err1 := fo.Invoke2(ctx1, example2())
ctx4, cancel4 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel4()
val1, val2, val3, err1 := fo.Invoke3(ctx1, example3())
ctx5, cancel5 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel5()
val1, val2, val3, val4, err1 := fo.Invoke4(ctx1, example4())
ctx6, cancel6 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel6()
val1, val2, val3, val4, val5, err1 := fo.Invoke5(ctx1, example5())
ctx7, cancel7 := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel7()
val1, val2, val3, val4, val5, val6, err1 := fo.Invoke6(ctx1, example6())
A short cut to Invoke
with context.Context
that wrapped internally.
val, err := fo.InvokeWith(func() (string, error) {
time.Sleep(2 * time.Second)
return "John", nil
}, fo.WithContextTimeout(1*time.Second))
// val == ""
// err == context deadline exceeded
InvokeWith* has the same behavior as InvokeWith, but returns multiple values.
func example0() (error) {}
func example1() (int, error) {}
func example2() (int, string, error) {}
func example3() (int, string, time.Date, error) {}
func example4() (int, string, time.Date, bool, error) {}
func example5() (int, string, time.Date, bool, float64, error) {}
func example6() (int, string, time.Date, bool, float64, byte, error) {}
err1 := fo.InvokeWith0(ctx1, example0(), fo.WithContextTimeout(1*time.Second))
val1, err1 := fo.InvokeWith1(ctx1, example1(), fo.WithContextTimeout(1*time.Second)) // alias to InvokeWith
val1, val2, err1 := fo.InvokeWith2(ctx1, example2(), fo.WithContextTimeout(1*time.Second))
val1, val2, val3, err1 := fo.InvokeWith3(ctx1, example3(), fo.WithContextTimeout(1*time.Second))
val1, val2, val3, val4, err1 := fo.InvokeWith4(ctx1, example4(), fo.WithContextTimeout(1*time.Second))
val1, val2, val3, val4, val5, err1 := fo.InvokeWith5(ctx1, example5(), fo.WithContextTimeout(1*time.Second))
val1, val2, val3, val4, val5, val6, err1 := fo.InvokeWith6(ctx1, example6(), fo.WithContextTimeout(1*time.Second))
A short cut to Invoke
with context.Context
that wrapped internally and defaults to set fo.WithContextTimeout(...)
.
val, err := fo.InvokeWithTimeout(func() (string, error) {
time.Sleep(2 * time.Second)
return "John", nil
}, 1*time.Second)
// val == ""
// err == context deadline exceeded
InvokeWithTimeout* has the same behavior as InvokeWithTimeout, but returns multiple values.
func example0() (error) {}
func example1() (int, error) {}
func example2() (int, string, error) {}
func example3() (int, string, time.Date, error) {}
func example4() (int, string, time.Date, bool, error) {}
func example5() (int, string, time.Date, bool, float64, error) {}
func example6() (int, string, time.Date, bool, float64, byte, error) {}
err1 := fo.InvokeWithTimeout0(ctx1, example0(), 1*time.Second)
val1, err1 := fo.InvokeWithTimeout1(ctx1, example1(), 1*time.Second) // alias to InvokeWithTimeout
val1, val2, err1 := fo.InvokeWithTimeout2(ctx1, example2(), 1*time.Second)
val1, val2, val3, err1 := fo.InvokeWithTimeout3(ctx1, example3(), 1*time.Second)
val1, val2, val3, val4, err1 := fo.InvokeWithTimeout4(ctx1, example4(), 1*time.Second)
val1, val2, val3, val4, val5, err1 := fo.InvokeWithTimeout5(ctx1, example5(), 1*time.Second)
val1, val2, val3, val4, val5, val6, err1 := fo.InvokeWithTimeout6(ctx1, example6(), 1*time.Second)
### May
Wraps a function call and filter out the error values and only returns with the result values.
```go
val := fo.May(time.Parse("2006-01-02", "2022-01-15"))
// 2022-01-15
val := fo.May(time.Parse("2006-01-02", "bad-value"))
// nil
May* has the same behavior as May, but returns multiple values.
func example0() (error)
func example1() (int, error)
func example2() (int, string, error)
func example3() (int, string, time.Date, error)
func example4() (int, string, time.Date, bool, error)
func example5() (int, string, time.Date, bool, float64, error)
func example6() (int, string, time.Date, bool, float64, byte, error)
fo.May0(example0())
val1 := fo.May1(example1()) // alias to May
val1, val2 := fo.May2(example2())
val1, val2, val3 := fo.May3(example3())
val1, val2, val3, val4 := fo.May4(example4())
val1, val2, val3, val4, val5 := fo.May5(example5())
val1, val2, val3, val4, val5, val6 := fo.May6(example6())
You can wrap functions like func (...) (..., ok bool)
with May*
and get the result values.
// math.Signbit(float64) bool
fo.May0(math.Signbit(v))
// bytes.Cut([]byte,[]byte) ([]byte, []byte, bool)
before, after := fo.May2(bytes.Cut(s, sep))
You can give context to the panic message by adding some printf-like arguments.
val, ok := any(someVar).(string)
fo.May1(val, ok, "someVar may be a string, got '%s'", val)
list := []int{0, 1, 2}
item := 5
fo.May0(lo.Contains[int](list, item), "'%s' may always contain '%s'", list, item)
...
Wraps a function call and filter out the error values and only returns with the result values,
behaves just like May(...)
and May\*(...)
, but with customizable handler and error collect
instead of using the package level internal handlers. Suitable for in-function and goroutine usage.
may := fo.NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "2022-01-15"))
// 2022-01-15
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
// nil
You could use may.Use(...)
to add handlers to the May
instance for better error handling.
may := fo.NewMay[time.Time]()
may.Use(func (err error, v ...any) {
fmt.Printf("error: %s\n", err)
})
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
// error: parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006"
may.Use(...)
supports chained calls.
may := fo.NewMay[time.Time]()
.Use(func (err error, v ...any) {
fmt.Printf("error from handler 1: %s\n", err)
})
.Use(func (err error, v ...any) {
fmt.Printf("error from handler 2: %s\n", err)
})
// multiple handlers will be called in order
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
// error from handler 1: parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006"
// error from handler 2: parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006"
fo
ships with some basic handler functions for common error handling.
may := fo.NewMay[string]()
.Use(fo.WithLoggerHandler(logger)) // log error with logger
.Use(fo.WithLogFuncHandler(log.Printf)) // log error with log.Printf
NewMay* has the same behavior as NewMay, but returns multiple values.
may := NewMay2[string, string]()
val1, val2 := may.Invoke(func () (string, bool, error) {
return "John", true, nil
})
// val1 == "John"
// val2 == true
Get the collected errors as a single error value from the May
instance.
may := fo.NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
err := may.CollectAsError() // error
// this error has been combined with go.uber.org/multierr.Combine(...).
// You can use errors.Is(err, someErr) to check if the error contains some error.
// Or just use multierr.Errors(err) to get the slice of errors.
This function is often useful when you want to return the error value only when all the invocations called / finished instead of checking the error value after each invocation.
may := NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
if err := may.CollectAsError(); err != nil {
return err
}
Get the collected errors as a errors slice value from the May
instance.
may := NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
errs := may.CollectAsErrors() // []error
fmt.Println(errs)
// []errors{
// parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006",
// parsing time "bad-value2" as "2006-01-02": cannot parse "bad-value2" as "2006",
// }
Sometimes you may find out you need a unique way to handle the errors from a May
instance,
instead of using the injected handlers with Use(...)
.
may := NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
may.HandleErrors(func(errs []error) {
fmt.Printf("error: %s\n", errs)
})
// error: [parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006" parsing time "bad-value2" as "2006-01-02": cannot parse "bad-value2" as "2006"]
Or perhaps you could return the collected errors when defer
ing the may.HandleErrors(...)
call.
func func1() (err error) {
may := NewMay[time.Time]()
defer may.HandleErrors(func(errs []error) {
err = fmt.Errorf("error: %s\n", errs)
})
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
return nil
}
func main() {
err := func1()
fmt.Println(err)
}
// error: [parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006" parsing time "bad-value2" as "2006-01-02": cannot parse "bad-value2" as "2006"]
may.HandleErrorsWithReturn(...)
is similar to may.HandleErrors(...)
, but it returns the handled errors.
may := NewMay[time.Time]()
val := may.Invoke(time.Parse("2006-01-02", "bad-value"))
val2 := may.Invoke(time.Parse("2006-01-02", "bad-value2"))
err := may.HandleErrorsWithReturn(func(err []error) error {
return fmt.Errorf("error: %s\n", err)
})
fmt.Println(val, val2, err)
// 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC error: [parsing time "bad-value" as "2006-01-02": cannot parse "bad-value" as "2006" parsing time "bad-value2" as "2006-01-02": cannot parse "bad-value2" as "2006"]
- implement more testable examples
- add playground link to README
- add benchmark tests
- maybe use sync.Pool for package level May instances
- Fork the project
- Fix open issues or request new features
Helper naming: helpers must be self explanatory and respect standards (other languages, libraries...). Feel free to suggest many names in your contributions.
Copyright Β© 2023 Neko Ayaka.
This project is MIT licensed.