-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: slices: func CollectError #70631
Comments
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
Similar (but more general) idea: #70084 (comment) var err error
slice := slices.Collect(iter.ErrorAbsorber(ErrorIter(), &err))
if err != nil {
// error handling
} |
While I acknowledge that an iterator over for v, err := range Something() {
if err != nil {
return err // or save the error somewhere and break? or ...?
}
// do something with v
} It feels weird to me to introduce a helper function that assumes a pattern that doesn't actually exist yet, particularly if it functions as a "backdoor" way to introduce a new pattern without proposing that pattern explicitly. The original proposal for iterators and sequences had some discussion about some different ways that fallible sequences could be represented (starting at #61405 (comment)), including Edit: Thanks to some cross-linking work by others, I think #70084 has become the current "main place" to discuss what the pattern ought to be for fallible sequences. FWIW I do quite like the general idea of a Therefore I was inspired to think about other ways this could potentially work, and the following is one. I'm not actually proposing to do this yet, since it seems to be rather awkward, but I'm posting it anyway to try to add something more concrete to the discussion than just a broad concern about patterns. The main thing I care about is actually settling on an idiom for fallible sequences before blessing that idiom with a helper function. One of the other possible patterns discussed in that previous thread was something shaped like sc := bufio.NewScanner(...)
for sc.Next() {
// ...
}
if err := sc.Err(); err != nil {
return err
} It's awkward to adapt that to range-over-func because a function pointer can't directly carry data beyond the actual function pointer and the function's closure. A range-over-func-shaped version of this would presumably involve a separate type with two methods, which I'll write as an interface here just for notation convenience: package iter
interface FallibleSeq[T any] {
Items() Seq[T]
Err() error
}
package slices
func CollectError[T any](seq iter.FallibleSeq[T]) ([]T, error) {
result := slices.Collect[T](seq.Items())
return seq.Err()
} Under this pattern it would be up to the (The original discussion of this particular shape of fallible iterator noted that it's easy to forget to check the error after iteration, and that remains true here. I don't intend this to be a solution to that problem, only to describe what |
One thing I had been thinking about: given that a fallible sequence will necessarily return a garbage value, it should almost necessarily return the garbage value, along with an error. We could easily expect that these would be wrapped. type Maybe[T any] struct{
val T
err error
}
func(m Maybe[T]) Values() (T, err) {...} So using the same exact mechanism as the infallible iterators. var Err error
for i, v:= range it{
vv, err:= v.Values()
if err! = nil{ Err = err; break}
//...
} Then the only thing that may need to be adapted is the iterator. Does this idea make sense or am I overlooking something, especially regarding the value conversion? Seems a bit too obvious. |
I can see the benefit to exposing the returned value alongside the failed element. By adding another func CollectError[T any](seq iter.Seq2[T, error]) ([]T, error) {
var err error
result := slices.Collect[T](func(yield func(t T) bool) {
for k, v := range seq {
if v != nil {
err = v
yield(k)
return
}
if !yield(k) {
return
}
}
})
return result, err
} |
I appreciate the other feedback too. If the jury is out on the idiomatic way to implement fallable errors, perhaps this proposal is premature - though my personal opinion is that the decision of whether a |
If the function that returns a fallible iterator also returns an error function along with the iterator, you don't need
|
With this discussion now unfortunately split across two issues I guess I'll redundantly note here that I ended up implementing that The // assuming that "seq" is a FallibleSeq[T]
s := slices.Collect(seq.Items())
if err := seq.Err(); err != nil {
// handle err
} Of course, this comes with the tradeoff that nothing is forcing you to actually call If (I think @jba's design also marginally benefits from |
Proposal Details
Idiomatic go functions are often in the form
func (...) (T, err)
.When creating iterators, it is equally common for the underlying functions to potentially return an error as each element in the iteration is created. For example, records might be read from a database, or requests from a client, etc.
Therefore, it will be relatively common for iterators of the form
iter.Seq2[T, error]
to be defined, as each element may produce an error. Sometimes a non-nil error will indicate a problem with that element only, but in many situations it will correspond to a termination of the iterator.slices.Collect()
is a helpful function to generate[]T
fromiter.Seq[T]
. It would be helpful to have an analogue to handleiter.Seq2[T, error]
My proposal is to add something similar to the following to the
slices
package:If an iteration includes a non-nil error, a slice of
[]T
will be returned including all elements up to (but not including) the erroring iteration, along with the error.Should no error be found, it will return
([]T, nil)
.While this is not a large or complex function, iterator syntax is a little unwieldy and this would allow gophers to more easily consume
iter.Seq2[T, err]
iterators without being exposed toyield
.As mentioned earlier, some
iter.Seq2[T, err]
iterators will return a non-nil error and then continue. They would not be suitable to use withCollectError
- but it would not be encouraged to collect these elements into a slice anyway; they should be processed as they arrive instead.The text was updated successfully, but these errors were encountered: