diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c643711..d1a67bc 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,6 +16,7 @@ │ ├── AssemblyInfo.fs – assembly metadata │ ├── ProcessingOptions.fs – processing configuration helpers │ ├── Observable.fs – observable wrappers/extensions +│ ├── ObservableOption.fs – `option` variants of the `Observable` functions (`choose`) │ ├── ObservableFactories.fs – factories reachable as `Observable.xxx` (`ofSeq`) │ ├── AsyncObservable.fs – async observable helpers │ └── TaskObservable.fs – task-based observable helpers diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d4f319..9ed6f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `Observable.choose` as a function of the `Observable` module; like every function of the library that works with optional values it takes a `voption` chooser, and the `option` variant is `ObservableOption.choose` - `Observable.ofSeq (items)` and `Observable.ofSeq (items, cancellationToken)`, reachable after `open FSharp.Control.R3` - `rxqueryWith cancellationToken` and `RxQueryBuilder (cancellationToken)` to cancel the query operators that return a task ### Changed - **Breaking:** `AwaitOperationConfiguration` cases are prefixed with `Await` (`AwaitSequential`, `AwaitParallel 4`, ...) so they no longer collide with `System.Threading.Tasks.Parallel` -- **Breaking:** the `Observable.Extensions` type is replaced by `Observable.ofSeq` +- **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 - **Breaking:** 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 - **Breaking:** `rxquery` `sumBy` adds values of the projected type: it requires `(+) : 'Value * 'Value -> 'Value` - `mapAsync` and `iterAsync` validate the concurrency limit of the options when they are called diff --git a/src/FSharp.Control.R3/FSharp.Control.R3.fsproj b/src/FSharp.Control.R3/FSharp.Control.R3.fsproj index cb270e1..d60e00f 100644 --- a/src/FSharp.Control.R3/FSharp.Control.R3.fsproj +++ b/src/FSharp.Control.R3/FSharp.Control.R3.fsproj @@ -19,6 +19,7 @@ + diff --git a/src/FSharp.Control.R3/Observable.fs b/src/FSharp.Control.R3/Observable.fs index 3078610..551a998 100644 --- a/src/FSharp.Control.R3/Observable.fs +++ b/src/FSharp.Control.R3/Observable.fs @@ -74,32 +74,24 @@ let inline take (count : int) (source) = ObservableExtensions.Take (source, coun /// Filters the observable elements of a sequence based on a predicate let inline where ([] f : 'T -> bool) source = ObservableExtensions.Where (source, f) -open System.Runtime.CompilerServices - -[] -module OptionExtensions = - - [] - type Observable private () = - - /// Applies the given function to each element of the observable. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns Some(x) - [] - static member choose f = map f >> where Option.isSome >> map Option.get - -[] -module ValueOptionExtensions = - - [] - type Observable private () = - - /// Applies the given function to each element of the observable. Returns - /// a sequence comprised of the results "x" for each element where - /// the function returns ValueSome(x) - [] - static member choose f = map f >> where ValueOption.isSome >> map ValueOption.get +// choose is a module function rather than a static member of an extension type: a module function here shadows +// FSharp.Core's Observable.choose, which would otherwise win the name resolution of Observable.choose. +// Like every function of the library that works with optional values it takes value options, which do not allocate; +// the variants that take options live in the ObservableOption module. +/// +/// Applies the chooser to each element and emits the values of the results +/// that hold one. +/// +/// The variant that takes an chooser is +/// . +/// +/// +let inline choose ([] chooser : 'T -> 'R voption) (source : Observable<'T>) = + source + |> map chooser + |> filter ValueOption.isSome + |> map ValueOption.get /// The query builder and its instances. [] diff --git a/src/FSharp.Control.R3/ObservableOption.fs b/src/FSharp.Control.R3/ObservableOption.fs new file mode 100644 index 0000000..7531e28 --- /dev/null +++ b/src/FSharp.Control.R3/ObservableOption.fs @@ -0,0 +1,23 @@ +/// +/// Variants of the functions of that work with +/// instead of . +/// +/// The functions of that work with optional values take value options, +/// which do not allocate; use this module where options are already at hand. +/// +/// +[] +module FSharp.Control.R3.ObservableOption + +open R3 +open FSharp.Control.R3 + +/// +/// Applies the chooser to each element and emits the values of the results +/// that hold one. +/// +let inline choose ([] chooser : 'T -> 'R option) (source : Observable<'T>) = + source + |> Observable.map chooser + |> Observable.filter Option.isSome + |> Observable.map Option.get