Sitelet https://github.com/fsprojects/FSharp.Control.R3/compare/main...feat/task-to-array-to-list
Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fsprojects/FSharp.Control.R3
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: fsprojects/FSharp.Control.R3
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: feat/task-to-array-to-list
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 18 commits
  • 10 files changed
  • 2 contributors

Commits on Oct 4, 2026

  1. refactor!: prefix AwaitOperationConfiguration cases with Await

    Unqualified cases such as `Parallel 4` collided with
    `System.Threading.Tasks.Parallel` (FS1133) whenever that namespace was
    opened after `FSharp.Control.R3`. The `Await` prefix mirrors the `Chunk`
    prefix of `ChunkConfiguration` and removes the collision.
    
    BREAKING CHANGE: `Sequential`, `Drop`, `Switch`, `Parallel`,
    `SequentialParallel` and `ThrottleFirstLast` become `AwaitSequential`,
    `AwaitDrop`, `AwaitSwitch`, `AwaitParallel`, `AwaitSequentialParallel`
    and `AwaitThrottleFirstLast`.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 4, 2026
    Configuration menu
    Copy the full SHA
    cfc4d47 View commit details
    Browse the repository at this point in the history

Commits on Oct 5, 2026

  1. fix: validate the concurrency limit of mapAsync options eagerly

    R3 rejects a `MaxConcurrent` of 0 or below -1 only when the mapped
    sequence is subscribed, typically far away from the code that built the
    options. Both flavours of `mapAsync` (and hence `iterAsync`) now throw
    `ArgumentOutOfRangeException` when they are called, through an internal
    `ProcessingOptions.Validate`.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    7110fb7 View commit details
    Browse the repository at this point in the history
  2. fix(rxquery): emit yield/zero synchronously to keep element order

    `Yield` and `Zero` used `Observable.Return/Empty (TimeProvider.System)`,
    which R3 schedules on the thread pool. Every element of a query therefore
    hopped to another thread, `SelectMany` merged them in arbitrary order and
    the results arrived after the source had moved on, so `take`, `head`,
    `last`, `zip` and friends were nondeterministic. Both now emit
    synchronously on subscription.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    6dc3875 View commit details
    Browse the repository at this point in the history
  3. fix(rxquery)!: stop seeding sumBy with Unchecked.defaultof

    The fold started from `Unchecked.defaultof`, so a reference type that
    defines `(+)` received `null` as the first operand and faulted. The
    first element now seeds the sum; an empty source still yields the
    default value, so numeric and `TimeSpan` sums keep returning zero.
    
    BREAKING CHANGE: `sumBy` adds values of the projected type, so it
    requires `(+) : 'Value * 'Value -> 'Value`.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    d657825 View commit details
    Browse the repository at this point in the history
  4. feat(rxquery): add rxqueryWith to cancel the terminal query operators

    The query operators that return a task (`count`, `head`, `iter`, ...)
    called the R3 operators without a token, so a query over a hot source
    could not be cancelled and kept its subscription alive. `RxQueryBuilder`
    now carries a `CancellationToken` that every such operator observes;
    `rxquery` keeps using `CancellationToken.None` and
    `(rxqueryWith ct) { ... }` passes a token.
    
    The `Builders` module opens `System.Threading` for the token instead of
    repeating the file-level `open System`.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    1f025e7 View commit details
    Browse the repository at this point in the history
  5. docs(rxquery): document the query builder and every query operator

    Every member of `RxQueryBuilder` now has XML documentation that states
    what the query operator does and, for the operators that return a task,
    when it completes or fails; the `Builders` module is documented too.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    4b9f059 View commit details
    Browse the repository at this point in the history
  6. feat(observable)!: make ofSeq reachable as Observable.ofSeq

    `Observable.ofSeq` was undefined (FS0039) after `open FSharp.Control.R3`,
    because the member lived on a type in an AutoOpen module nested inside
    the non-AutoOpen `Observable` module.
    
    `ofSeq` moves to the new AutoOpen `ObservableFactories` module that sits
    directly in the namespace, with an overload taking a `CancellationToken`.
    Name resolution of `Observable.ofSeq` finds no such function in the
    modules named `Observable` and falls through to the type, the same way
    `Observable.ofTask` resolves after opening `FSharp.Control.R3.Task`.
    
    BREAKING CHANGE: the `Observable.Extensions` type is removed. After
    `open FSharp.Control.R3` the bare type name `Observable` refers to
    `FSharp.Control.R3.ObservableFactories.Observable`: write
    `open type R3.Observable` and extend `R3.Observable` by its full name.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    a3a2594 View commit details
    Browse the repository at this point in the history
  7. feat(observable)!: make Observable.choose take a voption chooser,…

    … add `ObservableOption`
    
    `Observable.choose` always resolved to FSharp.Core's `IObservable`
    version (FS0001) after `open FSharp.Control.R3`, because the R3
    variants were static members of types in AutoOpen modules nested inside
    the non-AutoOpen `Observable` module. Their `[<Extension>]` members also
    added `.choose` to every F# function value.
    
    `choose` is now a function of the `Observable` module, which shadows
    FSharp.Core's `Observable.choose`. Like every function of the library
    that works with optional values it takes a `voption` chooser, which does
    not allocate. The `option` variant lives in the new `ObservableOption`
    module, which requires qualified access: `ObservableOption.choose`.
    
    BREAKING CHANGE: the `OptionExtensions` and `ValueOptionExtensions`
    types are removed; `Observable.choose` takes `'T -> 'R voption`, use
    `ObservableOption.choose` for `'T -> 'R option`. After
    `open FSharp.Control.R3`, `Observable.choose` is the R3 function; use
    `Microsoft.FSharp.Control.Observable.choose` for `IObservable` and F#
    events.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    cdb1a60 View commit details
    Browse the repository at this point in the history
  8. feat(observable): add chunkByBoundaries for boundaries of any eleme…

    …nt type
    
    `ChunkWindowBoundaries` reuses the element type of the source for the
    boundary sequence, although R3 ignores the boundary values, so chunking
    by `Observable<Unit>` ticks or a trigger subject required a dummy `map`.
    `chunkByBoundaries` passes boundaries of any element type to R3.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    6de0d51 View commit details
    Browse the repository at this point in the history
  9. fix(observable): validate every chunk length eagerly

    R3 validates the window length of `Chunk(count)` when it is called, but
    not of the time-and-count overloads: a length of 0 made every element
    fail with `IndexOutOfRangeException` through `OnErrorResume`, and a
    negative one failed only at subscription with `OverflowException`.
    Where R3 does validate, it reports its message as the parameter name
    and leaves out the rejected value.
    
    `chunkBySize` and `chunkBy` now reject a non-positive length of
    `ChunkCount`, `ChunkTimeSpanCount` and `ChunkMillisecondsCount` when they
    are called, with an `ArgumentOutOfRangeException` that names the
    parameter and carries the rejected length. Both functions are no longer
    inline, because the shared check is private.
    
    Their XML docs are rewritten along with them: they document the
    `ArgumentOutOfRangeException` and the last chunk emitted on completion,
    and drop the `ArgumentNullException` tag.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    926f7ad View commit details
    Browse the repository at this point in the history
  10. docs: document the processing and chunk configuration types

    - `AwaitParallel` and `AwaitSequentialParallel` send up to
      `MaxConcurrent` values to the selector at once and queue the others,
      instead of sending all values at once.
    - `ProcessingOptions`: document every field, `Default`, `Parallel`,
      `MaxConcurrent` and `AwaitOperation`. `ConfigureAwait` configures only
      R3's await of the selector result, not the awaits inside the selector,
      and `CancelOnCompleted` also discards the queued elements.
    - `ChunkConfiguration`: document every case and case field, and point
      from `ChunkWindowBoundaries` to `chunkByBoundaries`; the helpers read
      the default time provider when they are called.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    cb9fb30 View commit details
    Browse the repository at this point in the history
  11. docs(observable): correct misleading XML docs and document every func…

    …tion
    
    - `catch`: the remark "Exception does not stop further processing" was
      wrong; the handler only sees terminal failures of its exception type,
      the failed source is not resumed and `OnErrorResume` passes through.
    - `concat`: state that `a |> concat b` emits `b` first.
    - `bind`: an inner failure fails the result only while the source runs.
    - `cast`: an invalid cast is reported through `OnErrorResume`, and a
      null element cast to a value type as a `NullReferenceException`.
    - `skip`/`take`: a negative count throws when called; `take 0` completes
      at once without subscribing.
    - Document the module, `ofType` and the error behaviour of `map`,
      `filter` and `where`, and fix the copied typos ("identy", "upn", the
      question mark in `take`).
    - Name the `mapi` lambda parameters after the element and the index
      that R3 passes; the behaviour is unchanged.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    b54156b View commit details
    Browse the repository at this point in the history
  12. fix(async)!: remove the ignored cancellationToken from toLookup

    The four `Async` `toLookup` overloads declared an untyped
    `[<Optional>] cancellationToken` that they never used. Being generic, it
    also swallowed a positional third or fourth argument, so
    `Observable.toLookup (source, keySelector, comparer)` silently ignored
    the comparer and a positional `elementSelector` was dropped.
    
    The parameter is removed: like every other `Async` function the
    overloads observe the token of the computation, and positional
    arguments now resolve like in the `Task` flavour. The type also drops
    its unused `[<Extension>]` attribute, since none of its members is an
    extension member.
    
    BREAKING CHANGE: callers passing `cancellationToken =` by name must
    start the computation with that token instead.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    6731321 View commit details
    Browse the repository at this point in the history
  13. fix(async)!: surface the original exception and cancellation from Asy…

    …nc wrappers
    
    Every `Async` terminal function ended with `Async.AwaitTask`, which
    raises the `AggregateException` of a faulted task and turns a cancelled
    task into a failure. `try ... with :? InvalidOperationException` inside
    `async` therefore never matched, while the `Task` flavour surfaced the
    R3 exception directly.
    
    An internal helper keeps `Async.AwaitTask`, which resumes on the
    synchronization context that was current when the computation started
    to wait, and only translates its outcome: the single inner exception of
    an `AggregateException` is raised through the continuations, which keeps
    its stack trace, and a cancelled task cancels the computation. Both
    flavours now have the same error surface. `toArray` is no longer inline,
    because the helper is internal.
    
    BREAKING CHANGE: code that caught `AggregateException` from the `Async`
    functions now receives the inner exception, and a cancelled operation
    cancels the computation instead of failing it.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    b841076 View commit details
    Browse the repository at this point in the history
  14. fix: wait for iterAsync through ForEachAsync instead of CountAsync

    Both flavours of `iterAsync` waited for completion by counting the
    mapped elements, and R3 counts with a `checked` add, so a long-lived
    source with more than `Int32.MaxValue` elements faulted with
    `OverflowException` although nothing asked for a count. They now wait
    through `iter`, which ignores the elements.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    e02cc31 View commit details
    Browse the repository at this point in the history
  15. fix: stop iterAsync reliably and never lose a failure of its action

    Composing R3's `SelectAwait` with `ForEachAsync` left four gaps in both
    flavours of `iterAsync`:
    
    - Over a synchronous source, R3 attaches the terminal to the mapped stage
      only after `Subscribe` returns, so the action kept running for every
      element after it had thrown.
    - With an already cancelled token the action still ran for every element
      of a synchronous source, although the iteration was cancelled.
    - R3 drops a failure of the action that happens after the source
      completed, so the iteration completed successfully.
    - `SelectAwait` swallows an `OperationCanceledException`: a timeout such
      as HttpClient's `TaskCanceledException` stopped the sequential worker
      for good and `iterAsync` never completed.
    
    The action's exceptions no longer travel through R3 at all. An internal
    `IterationGuard` records the first one, skips the remaining actions and
    cancels an internal token that completes the iteration through
    `TakeUntil`, which also unsubscribes from the source at once; the
    iteration then fails with the recorded exception. A cancellation that R3
    itself caused for an invocation (`AwaitSwitch`, `CancelOnCompleted`,
    disposal) is not a failure, and a cancelled token cancels the iteration
    without subscribing.
    
    The `iterAsync` docs now describe which elements reach the action, when
    the iteration completes, also with `CancelOnCompleted`, and how it fails.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    5ce361d View commit details
    Browse the repository at this point in the history
  16. fix(task)!: default ofTask configureAwait to true

    Both `ofTask` overloads declared `[<Optional>] configureAwait` without a
    default value, so an omitted argument passed `false`, while R3's
    `FromAsync`, `ofAsync`, `ProcessingOptions.Default` and
    `ChunkConfiguration.AsyncWindow` all default to `true`. A UI handler
    subscribed through `ofTask` therefore ran off its synchronization
    context. The parameter now defaults to `true`, and both overloads are
    documented.
    
    BREAKING CHANGE: callers that omit `configureAwait` now resume on the
    captured synchronization context; pass `false` to keep the old
    behaviour.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    8245ea4 View commit details
    Browse the repository at this point in the history
  17. feat(task)!: move toArray and toList into the Task.Observable m…

    …odule
    
    As static members of the extension type, `Observable.toArray` and
    `Observable.toList` always bound to the `Async` module functions when
    both flavours were opened (module lookup wins over type lookup), and
    `Task.Observable.toArray` did not exist. They are now curried functions
    of the `Task.Observable` module that take the token first, like
    `length`, `iter` and the other `Task` functions.
    
    The `Extensions` type also drops its unused `[<Extension>]` attribute
    and annotates the `toLookup` parameters like the Async type does; the
    compiled signatures of `toLookup` do not change.
    
    BREAKING CHANGE: `Observable.toArray (source, ct)` becomes
    `Observable.toArray ct source`, likewise for `toList`.
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    xperiandri and claude committed Oct 5, 2026
    Configuration menu
    Copy the full SHA
    9ce0c5c View commit details
    Browse the repository at this point in the history
Loading