BooleanCat / go-functional

go-functional is a library of iterators to augment the standard library
MIT License
447 stars 23 forks source link

[Feature 🔨]: Support errors when filtering (e.g. `TryFilter`) #135

Closed danail-branekov closed 2 months ago

danail-branekov commented 2 months ago

Is your feature request related to a problem? Please describe.

Checking whether an object is applicable to a Filter predicate could theorethically yield erorrs.

For example, in Korifi we wanted to filter service offering that are related to a broker. In order to check whether an offering is related to a broker one need to lookup the broker in the very filter predicate and that may fail.

Currently errors while filtering is not supported (as the filter predicate cannot return errors), tehrefore we ended up with this ugly code

Describe the solution you'd like

Maybe a TryFilter function, similarly to TryCollect

Provide code snippets to show how this new feature might be used.

it.TryFilter(predicate_that_returns_bool_and_error).TryCollect() -> the error returned could accumulate errors from both Filter and Collect

Does this incur a breaking change?

No

Do you intend to build this feature yourself? We could give it a try

@georgethebeatle

BooleanCat commented 2 months ago

Thanks @danail-branekov

So you have an iter.Seq[V] that then needs to be raised so that will become an iter.Seq[V, error] after filtering?

I suppose there's a use case for both this and and Map that raises an iter.Seq into an iter.Seq2. This could be a good idea. It would end up looking something like (I'm not sure about the name yet):

func FilterError[V any](delegate func(func(V) bool), predicate func(V) (bool, error)) iter.Seq2[V, error] {
    return func(yield func(V) (bool, error)) {
        for value := range delegate {
            if ok, err := predicate(value); err != nil {
                yield(zero, err)
                return
            } else if ok {
                if !yield(value, nil) {
                    return
                }
            }
        }
    }
}

[!NOTE] It wouldn't be possible to make a chainable version of this for the itx package because Go's type system won't let us constrain a type paramter to error on a method or introduce a new generic type parameter on a method.

BooleanCat commented 2 months ago

We could also have a map iterator with this signature:

func MapExpand[V, W, X any](delegate func(func(V) bool), op func(V) (W, X)) iter.Seq2[W, X]

So even with this your example could be:

values, err := it.TryCollect(it.Filter2(it.MapExpand(slices.Values(mySlice), func(foo int) (int, error) {
  return foo, nil
}), myFilter))
BooleanCat commented 2 months ago

Actually that case may already be covered @danail-branekov!

values, err := it.TryCollect(it.Filter2(it.Zip(slices.Values(mySlice), it.Repeat[error](nil)), myFilter))

Essentially you create an iter.Seq2 by zipping your iterator with an infinite "nil" iterator and then you can use Filter2.

BooleanCat commented 2 months ago

@danail-branekov Is this what you had in mind?

https://github.com/BooleanCat/go-functional/pull/136

BooleanCat commented 2 months ago

Solved by #136