From 9ce0c5c812f6f8e0aa98207ea25b915cc2dddfb5 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Mon, 5 Oct 2026 02:13:27 +0200 Subject: [PATCH] feat(task)!: move `toArray` and `toList` into the `Task.Observable` module 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 `[]` 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 --- CHANGELOG.md | 1 + src/FSharp.Control.R3/TaskObservable.fs | 48 ++++++++++++++++++------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b73f924..0327dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Breaking:** `AwaitOperationConfiguration` cases are prefixed with `Await` (`AwaitSequential`, `AwaitParallel 4`, ...) so they no longer collide with `System.Threading.Tasks.Parallel` - **Breaking:** the `Async` functions raise the original exception instead of an `AggregateException`, and a cancelled operation cancels the computation instead of failing it - **Breaking:** the `Async` `toLookup` overloads no longer take a `cancellationToken`; they observe the token of the computation +- **Breaking:** the `Task` `toArray` and `toList` are curried functions of the `Task.Observable` module taking the token first: `Observable.toArray ct source` - **Breaking:** `ofTask` defaults `configureAwait` to `true`, like R3 and `ofAsync` - **Breaking:** the `OptionExtensions`/`ValueOptionExtensions` types are replaced by `Observable.choose`, which takes a `voption` chooser, and `ObservableOption.choose` for `option`; the `Observable.Extensions` type is replaced by `Observable.ofSeq` - **Breaking:** after `open FSharp.Control.R3`, `Observable.choose` is the R3 function, like `Observable.map` and `Observable.filter` already were; use `Microsoft.FSharp.Control.Observable.choose` for `IObservable` and F# events diff --git a/src/FSharp.Control.R3/TaskObservable.fs b/src/FSharp.Control.R3/TaskObservable.fs index ca77f4f..10990f7 100644 --- a/src/FSharp.Control.R3/TaskObservable.fs +++ b/src/FSharp.Control.R3/TaskObservable.fs @@ -104,13 +104,25 @@ module Observable = guard.ThrowIfFailed () } + // toArray and toList are curried module functions taking the token first, like every other function of this module. + // As static members of the extension type they were shadowed by the Async module functions whenever both flavours were opened. + + /// Collects the elements of the source into an array once it completes. + let toArray (cancellationToken : CancellationToken) (source : Observable<'T>) = ObservableExtensions.ToArrayAsync (source, cancellationToken) + + /// Collects the elements of the source into a list once it completes. + let toList (cancellationToken : CancellationToken) (source : Observable<'T>) = task { + let! array = ObservableExtensions.ToArrayAsync (source, cancellationToken) + return List.ofArray array + } + [] module Extensions = - open System.Runtime.CompilerServices + open System.Collections.Generic open System.Runtime.InteropServices - [] + [] type Observable private () = /// @@ -139,23 +151,33 @@ module Extensions = = Observable.FromAsync (asyncFactory, configureAwait) - static member inline toArray (source, [] cancellationToken) = ObservableExtensions.ToArrayAsync (source, cancellationToken) - - static member toList (source, [] cancellationToken) = task { - let! array = ObservableExtensions.ToArrayAsync (source, cancellationToken) - return List.ofArray array - } - - static member toLookup (source, keySelector : 'T -> 'Key, [] cancellationToken) = + static member toLookup (source : Observable<'T>, keySelector : 'T -> 'Key, [] cancellationToken : CancellationToken) = ObservableExtensions.ToLookupAsync (source, keySelector, cancellationToken) - static member toLookup (source, keySelector : 'T -> 'Key, keyComparer, [] cancellationToken) = + static member toLookup + ( + source : Observable<'T>, + keySelector : 'T -> 'Key, + keyComparer : IEqualityComparer<'Key>, + [] cancellationToken : CancellationToken + ) + = ObservableExtensions.ToLookupAsync (source, keySelector, keyComparer = keyComparer, cancellationToken = cancellationToken) - static member toLookup (source, keySelector : 'T -> 'Key, elementSelector : 'T -> 'Element, [] cancellationToken) = + static member toLookup + (source : Observable<'T>, keySelector : 'T -> 'Key, elementSelector : 'T -> 'Element, [] cancellationToken : CancellationToken) + = ObservableExtensions.ToLookupAsync (source, keySelector, elementSelector = elementSelector, cancellationToken = cancellationToken) - static member toLookup (source, keySelector : 'T -> 'Key, elementSelector : 'T -> 'Element, keyComparer, [] cancellationToken) = + static member toLookup + ( + source : Observable<'T>, + keySelector : 'T -> 'Key, + elementSelector : 'T -> 'Element, + keyComparer : IEqualityComparer<'Key>, + [] cancellationToken : CancellationToken + ) + = ObservableExtensions.ToLookupAsync ( source, keySelector,