lo
lo copied to clipboard
Error handling variants for iteratees
Quite frequently, I want to do something like lo.Map(slice, Transformer)
but Transformer
returns (R, error)
.
I can write a function literal and handle the error, but it's still not ergonomic as there's no proper way to return the error.
So, I propose (and I can submit a PR for this) a variant of Map (and maybe others that make sense) that looks like this:
func Map[T any, R any](collection []T, iteratee func(T, int) (R, error)) ([]R, error)
Where, if the iteratee returns an error, the entire iteration stops, returns an empty list and an error (or maybe returns what it has so far)
https://github.com/samber/lo/pull/43
We also want this for GroupBy and other lo functions.
Workaround:
var groupByError error
entityIDsByType := lo.GroupBy(input.EntityIds, func(entityID entity.EntityID) entity.EntityType {
entityType, err := entity.EntityTypeFromEntity(entityID)
if err != nil {
groupByError = err
}
return entityType
})
if groupByError != nil {
return nil, groupByError
}
What we would like to write:
entityIDsByType, err := lo.GroupBy(input.EntityIds, func(entityID entity.EntityID) (entity.EntityType, err) {
return entity.EntityTypeFromEntity(entityID)
})
if err != nil {
return nil, err
}
here's what we use when we need error handling:
type (
MapIteratee[T any, R any] func(T) R
MapErrIteratee[T any, R any] func(T) (R, error)
)
// Map is stolen from samber/lo but improved with errpr handling and no i arg.
func Map[T any, R any](
collection []T,
iteratee MapIteratee[T, R],
) []R {
result := make([]R, len(collection))
for i, item := range collection {
result[i] = iteratee(item)
}
return result
}
// MapErr is Map but handles errors.
func MapErr[T any, R any](
collection []T,
iteratee MapErrIteratee[T, R],
) ([]R, error) {
result := make([]R, len(collection))
for i, item := range collection {
var err error
result[i], err = iteratee(item)
if err != nil {
return nil, err
}
}
return result, nil
}
func Filter[V any](
collection []V,
predicate func(V) bool,
) []V {
result := []V{}
for _, item := range collection {
if predicate(item) {
result = append(result, item)
}
}
return result
}
func FilterErr[V any](
collection []V,
predicate func(V) (bool, error),
) ([]V, error) {
result := []V{}
for _, item := range collection {
ok, err := predicate(item)
if err != nil {
return nil, err
}
if ok {
result = append(result, item)
}
}
return result, nil
}
// Reduce is stolen from samber/lo but improved with proper error handling and
// without the useless `i` argument.
//
// Most of the time you just want to Reduce(arr, DataTransformer) instead of
// constructing a whole new function and ignoring the `i int` parameter.
//
// Also, supports returning errors so you can break out of the iteration.
func Reduce[T any, R any](
collection []T,
accumulator ReduceIteratee[T, R],
initial R,
) R {
for _, item := range collection {
initial = accumulator(initial, item)
}
return initial
}
func ReduceErr[T any, R any, E error](
collection []T,
accumulator ReduceErrIteratee[T, R, E],
initial R,
) (R, error) {
for _, item := range collection {
var err error
initial, err = accumulator(initial, item)
if err != nil {
return initial, err
}
}
return initial, nil
}
@Southclaws this looks great - it would be nice if this were a part of lo - or published as a package somewhere.
@Southclaws this looks great - it would be nice if this were a part of lo - or published as a package somewhere.
made a quick package, but it would be great to have these error variants as part of lo: https://github.com/Southclaws/dt/tree/main
Another naming option is just the E
suffix, like what Cobra uses.
I'd like to follow up on this discussion and suggest my own variation (which is very similar to @Southclaws's suggestion):
// MapErr is similar to lo.Map, but handles error in iteratee function
func MapErr[T any, R any](collection []T, iteratee func(T, int) (R, error)) ([]R, error) {
result := make([]R, len(collection))
for i, item := range collection {
res, err := iteratee(item, i)
if err != nil {
return nil, err
}
result[i] = res
}
return result, nil
}
If accepted I'd like to suggest my own contribution (similar to my implementation in https://github.com/otterize/lox) and cover further iterator cases (supporting FilterErr, ReduceErr, etc).
@samber would love your thoughts on this.
Hi, what's the status here? I'm, of course, willing to help if needed but there's been already several PRs open to implement this (https://github.com/samber/lo/pull/292, https://github.com/samber/lo/pull/43).
I think it's quite common to have fallible iteratees in Go and a MapErr
function was the only utility function I was unable to replace with lo. My implementation is actually identical to the @amitlicht's one.
Thank you.