diff --git a/release-notes.txt b/release-notes.txt
index d07e4fc..84f14dc 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -1,6 +1,9 @@
Release notes:
+1.1.0
+ - adds TaskSeq.chooseV, TaskSeq.chooseVAsync, #385
+
1.0.0
- adds taskSeqDynamic computation expression and TaskSeqDynamic/TaskSeqDynamicInfo types for dynamic (FSI-compatible) resumable code, fixing issue where taskSeq would raise NotImplementedException in F# Interactive, #246
- perf: TaskSeq.chunkBy and chunkByAsync reuse the ResizeArray buffer between chunks, reducing allocations on sequences with many chunk boundaries
diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
index 029fc91..31e06a6 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -12,6 +12,7 @@
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.ChooseV.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.ChooseV.Tests.fs
new file mode 100644
index 0000000..dc1783a
--- /dev/null
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.ChooseV.Tests.fs
@@ -0,0 +1,246 @@
+module TaskSeq.Tests.ChooseV
+
+open System
+
+open Xunit
+open FsUnit.Xunit
+
+open FSharp.Control
+
+//
+// TaskSeq.chooseV
+// TaskSeq.chooseVAsync
+//
+
+module EmptySeq =
+ []
+ let ``Null source is invalid`` () =
+ assertNullArg
+ <| fun () -> TaskSeq.chooseV (fun _ -> ValueNone) null
+
+ assertNullArg
+ <| fun () -> TaskSeq.chooseVAsync (fun _ -> Task.fromResult ValueNone) null
+
+ [)>]
+ let ``TaskSeq-chooseV`` variant = task {
+ let! empty =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.chooseV (fun _ -> ValueSome 42)
+ |> TaskSeq.toListAsync
+
+ List.isEmpty empty |> should be True
+ }
+
+ [)>]
+ let ``TaskSeq-chooseVAsync`` variant = task {
+ let! empty =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.chooseVAsync (fun _ -> task { return ValueSome 42 })
+ |> TaskSeq.toListAsync
+
+ List.isEmpty empty |> should be True
+ }
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-chooseV can convert and filter`` variant = task {
+ let chooser number =
+ if number <= 5 then
+ ValueSome(char number + '@')
+ else
+ ValueNone
+
+ let ts = Gen.getSeqImmutable variant
+
+ let! letters1 = TaskSeq.chooseV chooser ts |> TaskSeq.toArrayAsync
+ let! letters2 = TaskSeq.chooseV chooser ts |> TaskSeq.toArrayAsync
+
+ String letters1 |> should equal "ABCDE"
+ String letters2 |> should equal "ABCDE"
+ }
+
+ [)>]
+ let ``TaskSeq-chooseVAsync can convert and filter`` variant = task {
+ let chooser number = task {
+ return
+ if number <= 5 then
+ ValueSome(char number + '@')
+ else
+ ValueNone
+ }
+
+ let ts = Gen.getSeqImmutable variant
+
+ let! letters1 = TaskSeq.chooseVAsync chooser ts |> TaskSeq.toArrayAsync
+ let! letters2 = TaskSeq.chooseVAsync chooser ts |> TaskSeq.toArrayAsync
+
+ String letters1 |> should equal "ABCDE"
+ String letters2 |> should equal "ABCDE"
+ }
+
+module Immutable2 =
+ [)>]
+ let ``TaskSeq-chooseV returns all when chooser always returns ValueSome`` variant = task {
+ let ts = Gen.getSeqImmutable variant
+ let! xs = ts |> TaskSeq.chooseV ValueSome |> TaskSeq.toArrayAsync
+ xs |> should equal [| 1..10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-chooseVAsync returns all when chooser always returns ValueSome`` variant = task {
+ let ts = Gen.getSeqImmutable variant
+
+ let! xs =
+ ts
+ |> TaskSeq.chooseVAsync (fun x -> task { return ValueSome x })
+ |> TaskSeq.toArrayAsync
+
+ xs |> should equal [| 1..10 |]
+ }
+
+ [)>]
+ let ``TaskSeq-chooseV returns empty when chooser always returns ValueNone`` variant = task {
+ let ts = Gen.getSeqImmutable variant
+
+ do! ts |> TaskSeq.chooseV (fun _ -> ValueNone) |> verifyEmpty
+ }
+
+ [)>]
+ let ``TaskSeq-chooseVAsync returns empty when chooser always returns ValueNone`` variant = task {
+ let ts = Gen.getSeqImmutable variant
+
+ do!
+ ts
+ |> TaskSeq.chooseVAsync (fun _ -> task { return ValueNone })
+ |> verifyEmpty
+ }
+
+ []
+ let ``TaskSeq-chooseV with singleton sequence and ValueSome chooser returns singleton`` () = task {
+ let! xs =
+ taskSeq { yield 42 }
+ |> TaskSeq.chooseV (fun x -> ValueSome(x * 2))
+ |> TaskSeq.toListAsync
+
+ xs |> should equal [ 84 ]
+ }
+
+ []
+ let ``TaskSeq-chooseV with singleton sequence and ValueNone chooser returns empty`` () =
+ taskSeq { yield 42 }
+ |> TaskSeq.chooseV (fun _ -> ValueNone)
+ |> verifyEmpty
+
+ []
+ let ``TaskSeq-chooseV can change the element type`` () = task {
+ // choose maps int -> string voption, verifying type-changing behavior
+ let chooser n =
+ if n % 2 = 0 then
+ ValueSome(sprintf "even-%d" n)
+ else
+ ValueNone
+
+ let! xs =
+ taskSeq { yield! [ 1..6 ] }
+ |> TaskSeq.chooseV chooser
+ |> TaskSeq.toListAsync
+
+ xs |> should equal [ "even-2"; "even-4"; "even-6" ]
+ }
+
+ []
+ let ``TaskSeq-chooseVAsync can change the element type`` () = task {
+ let chooser n = task {
+ return
+ if n % 2 = 0 then
+ ValueSome(sprintf "even-%d" n)
+ else
+ ValueNone
+ }
+
+ let! xs =
+ taskSeq { yield! [ 1..6 ] }
+ |> TaskSeq.chooseVAsync chooser
+ |> TaskSeq.toListAsync
+
+ xs |> should equal [ "even-2"; "even-4"; "even-6" ]
+ }
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-chooseV applied multiple times`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let chooser x number =
+ if number <= x then
+ ValueSome(char number + '@')
+ else
+ ValueNone
+
+ let! lettersA = ts |> TaskSeq.chooseV (chooser 5) |> TaskSeq.toArrayAsync
+ let! lettersK = ts |> TaskSeq.chooseV (chooser 15) |> TaskSeq.toArrayAsync
+ let! lettersU = ts |> TaskSeq.chooseV (chooser 25) |> TaskSeq.toArrayAsync
+
+ String lettersA |> should equal "ABCDE"
+ String lettersK |> should equal "KLMNO"
+ String lettersU |> should equal "UVWXY"
+ }
+
+ [)>]
+ let ``TaskSeq-chooseVAsync applied multiple times`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let chooser x number = task {
+ return
+ if number <= x then
+ ValueSome(char number + '@')
+ else
+ ValueNone
+ }
+
+ let! lettersA = TaskSeq.chooseVAsync (chooser 5) ts |> TaskSeq.toArrayAsync
+ let! lettersK = TaskSeq.chooseVAsync (chooser 15) ts |> TaskSeq.toArrayAsync
+ let! lettersU = TaskSeq.chooseVAsync (chooser 25) ts |> TaskSeq.toArrayAsync
+
+ String lettersA |> should equal "ABCDE"
+ String lettersK |> should equal "KLMNO"
+ String lettersU |> should equal "UVWXY"
+ }
+
+ []
+ let ``TaskSeq-chooseV evaluates each source element exactly once`` () = task {
+ let mutable count = 0
+
+ let ts = taskSeq {
+ for i in 1..5 do
+ count <- count + 1
+ yield i
+ }
+
+ let! xs =
+ ts
+ |> TaskSeq.chooseV (fun x -> if x < 3 then ValueSome x else ValueNone)
+ |> TaskSeq.toListAsync
+
+ count |> should equal 5 // all 5 elements were visited
+ xs |> should equal [ 1; 2 ]
+ }
+
+ []
+ let ``TaskSeq-chooseVAsync evaluates each source element exactly once`` () = task {
+ let mutable count = 0
+
+ let ts = taskSeq {
+ for i in 1..5 do
+ count <- count + 1
+ yield i
+ }
+
+ let! xs =
+ ts
+ |> TaskSeq.chooseVAsync (fun x -> task { return if x < 3 then ValueSome x else ValueNone })
+ |> TaskSeq.toListAsync
+
+ count |> should equal 5
+ xs |> should equal [ 1; 2 ]
+ }
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index 9051ddc..d3a94f9 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -441,7 +441,9 @@ type TaskSeq private () =
}
static member choose chooser source = Internal.choose (TryPick chooser) source
+ static member chooseV chooser source = Internal.chooseV (TryPickV chooser) source
static member chooseAsync chooser source = Internal.choose (TryPickAsync chooser) source
+ static member chooseVAsync chooser source = Internal.chooseV (TryPickVAsync chooser) source
static member filter predicate source = Internal.filter (Predicate predicate) source
static member filterAsync predicate source = Internal.filter (PredicateAsync predicate) source
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index f914048..1caf826 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -1011,6 +1011,18 @@ type TaskSeq =
/// Thrown when the input task sequence is null.
static member choose: chooser: ('T -> 'U option) -> source: TaskSeq<'T> -> TaskSeq<'U>
+ ///
+ /// Applies the given function to each element of the task sequence. Returns
+ /// a sequence comprised of the results where the function returns .
+ /// If is asynchronous, consider using .
+ ///
+ ///
+ /// A function to transform items of type into value options of type .
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member chooseV: chooser: ('T -> 'U voption) -> source: TaskSeq<'T> -> TaskSeq<'U>
+
///
/// Applies the given asynchronous function to each element of the task sequence.
/// Returns a sequence comprised of the results where the function returns a result
@@ -1024,6 +1036,19 @@ type TaskSeq =
/// Thrown when the input task sequence is null.
static member chooseAsync: chooser: ('T -> #Task<'U option>) -> source: TaskSeq<'T> -> TaskSeq<'U>
+ ///
+ /// Applies the given asynchronous function to each element of the task sequence.
+ /// Returns a sequence comprised of the results where the function returns a result
+ /// of .
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// An asynchronous function to transform items of type into value options of type .
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member chooseVAsync: chooser: ('T -> #Task<'U voption>) -> source: TaskSeq<'T> -> TaskSeq<'U>
+
///
/// Returns a new task sequence containing only the elements of the collection
/// for which the given function returns .
diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
index 261dbba..c26d174 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
@@ -39,6 +39,11 @@ type internal ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U opti
| TryPick of try_pick: ('T -> 'U option)
| TryPickAsync of async_try_pick: ('T -> 'TaskOption)
+[]
+type internal ChooserVAction<'T, 'U, 'TaskValueOption when 'TaskValueOption :> Task<'U voption>> =
+ | TryPickV of try_pickv: ('T -> 'U voption)
+ | TryPickVAsync of async_try_pickv: ('T -> 'TaskValueOption)
+
[]
type internal PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> =
| Predicate of try_filter: ('T -> bool)
@@ -1055,6 +1060,25 @@ module internal TaskSeqInternal =
| None -> ()
}
+ let chooseV chooser (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+
+ match chooser with
+ | TryPickV picker ->
+ for item in source do
+ match picker item with
+ | ValueSome value -> yield value
+ | ValueNone -> ()
+
+ | TryPickVAsync picker ->
+ for item in source do
+ match! picker item with
+ | ValueSome value -> yield value
+ | ValueNone -> ()
+ }
+
let filter predicate (source: TaskSeq<_>) =
checkNonNull (nameof source) source