Skip to content

Option and Result over async streams: Choose, SequenceAsync and TraverseAsync on IAsyncEnumerable #128

Closed

Description

@CaffeinatedCoder

Choose() and Choose(selector) take an IEnumerable<T> (OptionExtensions.cs), and every TraverseAsync awaits one operation per element of a synchronous sequence (ResultExtensions.cs). Nothing in CodoMetis.TypeKit takes an IAsyncEnumerable<T>, so a paged API, a streamed import or an EF query enumerated with AsAsyncEnumerable() has to be buffered into a list before it reaches them, or unwrapped by hand with TryGetValue in an await foreach.

Proposal

  • Choose() over an IAsyncEnumerable<Option<T>> and Choose(selector) over an IAsyncEnumerable<T> with a selector returning Option<TResult>: lazy, in order, returning an IAsyncEnumerable<T>.
  • SequenceAsync(cancellationToken) over an IAsyncEnumerable<Result<T, TError>> (and Result<TError>): the successes as an IReadOnlyList<T>, or the first error, reading nothing after it.
  • TraverseAsync over an IAsyncEnumerable<T>, with the same overloads and the same one-at-a-time rule as the synchronous one, if the spike of the names below leaves room for it.

Before the code: .NET 10 ships System.Linq.AsyncEnumerable in the shared framework, so check that no name here binds ambiguously beside its operators, and that CMTK0003 reports a dropped Task<Result<...>> from SequenceAsync as it does for TraverseAsync.

Done when

  • Tests show cancellation reaching the enumerator (WithCancellation and the token parameter), the enumerator disposed on an early exit (the first error, a break, an exception from the selector), order kept, and an uninitialized Option/Result element refused as the synchronous overloads refuse it.
  • A null delegate is refused as everywhere else (NullDelegateTests covers the new members), and AsynchronousContextTests covers them.
  • The core README's sequence section has a compiled sample, each new member has its claim or waiver in test/oracles/, and the changelog says so.

Activity

  1. added this to the 2.3.0 milestone on Oct 29, 2026
  2. CaffeinatedCoder commented on Oct 29, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Done in #139, merged into release/2.3.0 (5eb60b7). TraverseAsync over a stream shipped too; no name binds ambiguously beside System.Linq.AsyncEnumerable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/option-resultOption, Result and their extensionsenhancementNew feature or requestpackage/coreAffects CodoMetis.TypeKit

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions