Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/FSharp.Control.R3/FSharp.Control.R3.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="AssemblyInfo.fs" />
<Compile Include="ProcessingOptions.fs" />
<Compile Include="Observable.fs" />
<Compile Include="ObservableOption.fs" />
<Compile Include="ObservableFactories.fs" />
<Compile Include="AsyncObservable.fs" />
<Compile Include="TaskObservable.fs" />
Expand Down
42 changes: 17 additions & 25 deletions src/FSharp.Control.R3/Observable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ([<InlineIfLambda>] f : 'T -> bool) source = ObservableExtensions.Where (source, f)

open System.Runtime.CompilerServices

[<AutoOpen>]
module OptionExtensions =

[<AbstractClass; Sealed; Extension>]
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)
[<Extension>]
static member choose f = map f >> where Option.isSome >> map Option.get

[<AutoOpen>]
module ValueOptionExtensions =

[<AbstractClass; Sealed; Extension>]
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)
[<Extension>]
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.

/// <summary>
/// Applies the chooser to each element and emits the values of the <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1"/> results
/// that hold one.
/// <para>
/// The variant that takes an <see cref="T:Microsoft.FSharp.Core.FSharpOption`1"/> chooser is
/// <see cref="M:FSharp.Control.R3.ObservableOption.choose``2(Microsoft.FSharp.Core.FSharpFunc{``0,Microsoft.FSharp.Core.FSharpOption{``1}},R3.Observable{``0})"/>.
/// </para>
/// </summary>
let inline choose ([<InlineIfLambda>] chooser : 'T -> 'R voption) (source : Observable<'T>) =
source
|> map chooser
|> filter ValueOption.isSome
|> map ValueOption.get

/// The <see cref="T:FSharp.Control.R3.Observable.BuildersModule.RxQueryBuilder"/> query builder and its instances.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
Expand Down
23 changes: 23 additions & 0 deletions src/FSharp.Control.R3/ObservableOption.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <summary>
/// Variants of the functions of <see cref="T:FSharp.Control.R3.Observable"/> that work with
/// <see cref="T:Microsoft.FSharp.Core.FSharpOption`1"/> instead of <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1"/>.
/// <para>
/// The functions of <see cref="T:FSharp.Control.R3.Observable"/> that work with optional values take value options,
/// which do not allocate; use this module where options are already at hand.
/// </para>
/// </summary>
[<RequireQualifiedAccess>]
module FSharp.Control.R3.ObservableOption

open R3
open FSharp.Control.R3

/// <summary>
/// Applies the chooser to each element and emits the values of the <see cref="T:Microsoft.FSharp.Core.FSharpOption`1"/> results
/// that hold one.
/// </summary>
let inline choose ([<InlineIfLambda>] chooser : 'T -> 'R option) (source : Observable<'T>) =
source
|> Observable.map chooser
|> Observable.filter Option.isSome
|> Observable.map Option.get