Proposal Details
The proposed API is a definition of a SeqError
, associated with an iteration or sequencing abnormality, located in the iter
package.
type SeqError func() error
func (SeqError) Error() string
func (SeqError) Unwrap() error
func (SeqError) Is(error) bool
var SeqHazard error // the target matched by SeqError.Is (notably, any custom error may define itself to match `SeqHazard`)
SeqError
is an enriched error type with the following properties:
- Context is added to the error string of a contained error.
- Dynamic inspection with errors.Unwrap
and errors.Is
is supported.
- Sequencing errors may be made statically manifest in some circumstances. For example, between iter.Seq2[T, error]
and iter.Seq2[T, iter.SeqError]
, the latter says something usefully more narrow.
- The error value remains nil-comparable, or may be lazily evaluated.
Additionally, a Throw
function synthesizing a SeqError
from an existing error is proposed.
func Throw(error) SeqError
The obvious use case is that an iterator may Throw
an error as a SeqError
where it would otherwise yield an error on an unhappy path. A quick sketch of where an iterator would throw:
func All() iter.Seq2[V, iter.SeqError] {
...
return func(yield func(V, SeqError) bool){
...
// an unhappy path
if err != nil {
// Throw a SeqError, rather than yield an error
yield(v, iter.Throw(err))
return
}
// happy path
if !yield(v, nil) {
return
}
...
}
}
Implementation would be short:
type SeqError func() error
func (e SeqError) Error() string {
return "sequencing hazard: " + e().Error()
}
func (e SeqError) Unwrap() error {
return e()
}
func (e SeqError) Is(target error) bool {
return target == SeqHazard
}
var SeqHazard error = errors.New("sequencing hazard")
func Throw(err error) SeqError {
if err == nil {
return nil
}
return SeqError(func() error {
return err
})
}
Adopting this proposal would be asking APIs that export fallible iterators to consistently Throw
, or otherwise provide something that statically or dynamically matches SeqError
, where appropriate. Assuming no convergence on how iteration errors are handled, maybe it's worth asking that fallible iterators define an iteration error in a common way.
Comment From: ianlancetaylor
Throw
doesn't seem like the best name here. In many languages throw
raises an exception or otherwise changes the flow of control. That is not true of this Throw
.
Comment From: AndrewHarrisSPU
Throw
doesn't seem like the best name here.
The worst name I came up with was SeqFault
.
I'm entirely open to a better name - Hazard
maybe? - where Throw
might be too squisy.
Comment From: anonld
Putting the Throw
function aside, how about proposal: errors: add UnwrapAll
?
func UnwrapAll(e error) iter.Seq[error] {
return func(yield func(error) bool) bool {
loop:
for {
if !yield(e) {
return false
}
switch x := e.(type) {
case interface{ Unwrap() error }:
e = x.Unwrap()
case interface{ Unwrap() []error }:
for _, e = range x.Unwrap() {
for e = range UnwrapAll(e) {
if !yield(e) {
return false
}
}
}
fallthrough
default:
break loop
}
}
return true
}
}
With UnwrapAll
, errors.Is
/As
can share the control flow code.
By the way, please correct me if the implementation details above get wrong.
Getting back to the Throw
function, you might want something like this:
func doAndThrow(is func(error) bool) {
do := func() error { ... }
for e := range UnwrapAll(do()) {
if is(e) {
panic(e)
}
}
}
To recover from the panic, just use defer
and recover
in the same way as you call the MustXXX
family in rexgep
and text/template
.
@AndrewHarrisSPU As I may have misinterpreted the goal of your proposal, would you mind further clarifying what problem you are trying to solve? If we are addressing different use cases, I will file another proposal then.
Comment From: earthboundkid
I don't understand why it's func() error
. ISTM, this is just a concrete error type and could be struct{ err error }
.
Comment From: AndrewHarrisSPU
I don't understand why it's
func() error
. ISTM, this is just a concrete error type and could bestruct{ err error }
.
Two details seemed to work out better for func() error
:
First, a SeqError
would be nil-comparable, and have a nil zero value. A pointer-to-struct {error}
is another idea, but it's a little unfortunate that missing the pointer-to part breaks if err != nil
.
Second, func() error
seemed modestly more flexible for a concurrently held iterator to implement. The conversion from func() error
to SeqError
, or vice-versa, is a little more immediate. E.G.: context.Err
's semantics wouldn't be right for every iterator, but Seq[T]
to a Seq[T, SeqError]
that just yields SeqError(context.Err)
has a coherent meaning when lazily evaluated: if the context shuts down, that's observed as a sequencing hazard.
Comment From: rsc
I don't understand why this machinery would be used instead of Seq2[T, error].
Comment From: AndrewHarrisSPU
I don't understand why this machinery would be used instead of Seq2[T, error].
I tried to formulate some finesse here - I would not to want to coerce the decision against Seq2[T, error] and for Seq2[T, SeqError], but leave it more open-ended. The idea would be, an implementation can opt into exporting the SeqError
form, or a user can convert into the SeqError
form. If the error
form is preferred, there is still some utility wrapping provided by consistently Throw
ing (and, a fallible iterator really should be able to identify where to Throw
).
What should be maintained invariantly is a static inference about errors in Seq2[T, SeqError]
- that they say something about a disruption to the overall state of the sequence, and not something non-disruptive to control flow, exclusively about a value of T.
I think that invariant is useful as a hook for readability and analysis, and as a premise for iterator composition. For example, @jba's error function idea could be functionally derived from Seq2[T, SeqError] more confidently than from Seq2[T, error]. Or, I worry that it's possible to lose track of the invariant with xiter
-style composition just parameterized by error
.
Comment From: rsc
This proposal has been declined as infeasible. — rsc for the proposal review group