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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="TaskSeq.Append.Tests.fs" />
<Compile Include="TaskSeq.Cast.Tests.fs" />
<Compile Include="TaskSeq.Choose.Tests.fs" />
<Compile Include="TaskSeq.ChooseV.Tests.fs" />
<Compile Include="TaskSeq.Collect.Tests.fs" />
<Compile Include="TaskSeq.Concat.Tests.fs" />
<Compile Include="TaskSeq.Contains.Tests.fs" />
Expand Down
246 changes: 246 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.ChooseV.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
module TaskSeq.Tests.ChooseV

open System

open Xunit
open FsUnit.Xunit

open FSharp.Control

//
// TaskSeq.chooseV
// TaskSeq.chooseVAsync
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.chooseV (fun _ -> ValueNone) null

assertNullArg
<| fun () -> TaskSeq.chooseVAsync (fun _ -> Task.fromResult ValueNone) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-chooseV`` variant = task {
let! empty =
Gen.getEmptyVariant variant
|> TaskSeq.chooseV (fun _ -> ValueSome 42)
|> TaskSeq.toListAsync

List.isEmpty empty |> should be True
}

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
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 =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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"
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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 =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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 |]
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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 |]
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-chooseV returns empty when chooser always returns ValueNone`` variant = task {
let ts = Gen.getSeqImmutable variant

do! ts |> TaskSeq.chooseV (fun _ -> ValueNone) |> verifyEmpty
}

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
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
}

[<Fact>]
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 ]
}

[<Fact>]
let ``TaskSeq-chooseV with singleton sequence and ValueNone chooser returns empty`` () =
taskSeq { yield 42 }
|> TaskSeq.chooseV (fun _ -> ValueNone)
|> verifyEmpty

[<Fact>]
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" ]
}

[<Fact>]
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 =
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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"
}

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
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"
}

[<Fact>]
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 ]
}

[<Fact>]
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 ]
}
2 changes: 2 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,18 @@ type TaskSeq =
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member choose: chooser: ('T -> 'U option) -> source: TaskSeq<'T> -> TaskSeq<'U>

/// <summary>
/// Applies the given function <paramref name="chooser" /> to each element of the task sequence. Returns
/// a sequence comprised of the results where the function returns <see cref="ValueSome(x)" />.
/// If <paramref name="chooser" /> is asynchronous, consider using <see cref="TaskSeq.chooseVAsync" />.
/// </summary>
///
/// <param name="chooser">A function to transform items of type <paramref name="'T" /> into value options of type <paramref name="'U" />.</param>
/// <param name="source">The input task sequence.</param>
/// <returns>The resulting task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member chooseV: chooser: ('T -> 'U voption) -> source: TaskSeq<'T> -> TaskSeq<'U>

/// <summary>
/// Applies the given asynchronous function <paramref name="chooser" /> to each element of the task sequence.
/// Returns a sequence comprised of the results where the function returns a <see cref="task" /> result
Expand All @@ -1024,6 +1036,19 @@ type TaskSeq =
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member chooseAsync: chooser: ('T -> #Task<'U option>) -> source: TaskSeq<'T> -> TaskSeq<'U>

/// <summary>
/// Applies the given asynchronous function <paramref name="chooser" /> to each element of the task sequence.
/// Returns a sequence comprised of the results where the function returns a <see cref="task" /> result
/// of <see cref="ValueSome(x)" />.
/// If <paramref name="chooser" /> is synchronous, consider using <see cref="TaskSeq.chooseV" />.
/// </summary>
///
/// <param name="chooser">An asynchronous function to transform items of type <paramref name="'T" /> into value options of type <paramref name="'U" />.</param>
/// <param name="source">The input task sequence.</param>
/// <returns>The resulting task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member chooseVAsync: chooser: ('T -> #Task<'U voption>) -> source: TaskSeq<'T> -> TaskSeq<'U>

/// <summary>
/// Returns a new task sequence containing only the elements of the collection
/// for which the given function <paramref name="predicate" /> returns <see cref="true" />.
Expand Down
24 changes: 24 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

[<Struct>]
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)

[<Struct>]
type internal PredicateAction<'T, 'TaskBool when 'TaskBool :> Task<bool>> =
| Predicate of try_filter: ('T -> bool)
Expand Down Expand Up @@ -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

Expand Down
Loading