Skip to content

Add TaskSeq.append, appendSeq and prependSeq #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 6, 2022
Merged
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
264 changes: 140 additions & 124 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="AssemblyInfo.fs" />
<Compile Include="Nunit.Extensions.fs" />
<Compile Include="TestUtils.fs" />
<Compile Include="TaskSeq.Append.Tests.fs" />
<Compile Include="TaskSeq.Cast.Tests.fs" />
<Compile Include="TaskSeq.Choose.Tests.fs" />
<Compile Include="TaskSeq.Collect.Tests.fs" />
Expand Down
115 changes: 115 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
module TaskSeq.Tests.Append

open System

open Xunit
open FsUnit.Xunit
open FsToolkit.ErrorHandling

open FSharp.Control
open System.Collections.Generic

//
// TaskSeq.append
// TaskSeq.appendSeq
// TaskSeq.prependSeq
//

let validateSequence ts =
ts
|> TaskSeq.toSeqCachedAsync
|> Task.map (Seq.map string)
|> Task.map (String.concat "")
|> Task.map (should equal "1234567891012345678910")

module EmptySeq =
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-append both args empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.append (Gen.getEmptyVariant variant)
|> verifyEmpty

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-appendSeq both args empty`` variant =
Seq.empty
|> TaskSeq.appendSeq (Gen.getEmptyVariant variant)
|> verifyEmpty

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-prependSeq both args empty`` variant =
Gen.getEmptyVariant variant
|> TaskSeq.prependSeq Seq.empty
|> verifyEmpty

module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-append`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.append (Gen.getSeqImmutable variant)
|> validateSequence

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-appendSeq with a list`` variant =
[ 1..10 ]
|> TaskSeq.appendSeq (Gen.getSeqImmutable variant)
|> validateSequence

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-appendSeq with an array`` variant =
[| 1..10 |]
|> TaskSeq.appendSeq (Gen.getSeqImmutable variant)
|> validateSequence

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-prependSeq with a list`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.prependSeq [ 1..10 ]
|> validateSequence

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-prependSeq with an array`` variant =
Gen.getSeqImmutable variant
|> TaskSeq.prependSeq [| 1..10 |]
|> validateSequence

module SideEffects =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-append consumes whole sequence once incl after-effects`` variant =
let mutable i = 0

taskSeq {
i <- i + 1
yield! [ 1..10 ]
i <- i + 1
}
|> TaskSeq.append (Gen.getSeqImmutable variant)
|> validateSequence
|> Task.map (fun () -> i |> should equal 2)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-appendSeq consumes whole sequence once incl after-effects`` variant =
let mutable i = 0

let ts = taskSeq {
i <- i + 1
yield! [ 1..10 ]
i <- i + 1
}

[| 1..10 |]
|> TaskSeq.appendSeq ts
|> validateSequence
|> Task.map (fun () -> i |> should equal 2)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-prependSeq consumes whole sequence once incl after-effects`` variant =
let mutable i = 0

taskSeq {
i <- i + 1
yield! [ 1..10 ]
i <- i + 1
}
|> TaskSeq.prependSeq [ 1..10 ]
|> validateSequence
|> Task.map (fun () -> i |> should equal 2)
60 changes: 59 additions & 1 deletion src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,69 @@ module EmptySeq =

module Immutable =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-concat with empty sequences`` variant =
let ``TaskSeq-concat with three sequences of sequences`` variant =
taskSeq {
yield Gen.getSeqImmutable variant // not yield-bang!
yield Gen.getSeqImmutable variant
yield Gen.getSeqImmutable variant
}
|> TaskSeq.concat
|> validateSequence

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-concat with three sequences of sequences and few empties`` variant =
taskSeq {
yield TaskSeq.empty
yield Gen.getSeqImmutable variant // not yield-bang!
yield TaskSeq.empty
yield TaskSeq.empty
yield Gen.getSeqImmutable variant
yield TaskSeq.empty
yield Gen.getSeqImmutable variant
yield TaskSeq.empty
yield TaskSeq.empty
yield TaskSeq.empty
yield TaskSeq.empty
}
|> TaskSeq.concat
|> validateSequence

module SideEffect =
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-concat consumes until the end, including side-effects`` variant =
let mutable i = 0

taskSeq {
yield Gen.getSeqImmutable variant // not yield-bang!
yield Gen.getSeqImmutable variant

yield taskSeq {
yield! [ 1..10 ]
i <- i + 1
}
}
|> TaskSeq.concat
|> validateSequence
|> Task.map (fun () -> i |> should equal 1)

[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
let ``TaskSeq-concat consumes side effects in empty sequences`` variant =
let mutable i = 0

taskSeq {
yield taskSeq { do i <- i + 1 }
yield Gen.getSeqImmutable variant // not yield-bang!
yield TaskSeq.empty
yield taskSeq { do i <- i + 1 }
yield Gen.getSeqImmutable variant
yield TaskSeq.empty
yield Gen.getSeqImmutable variant
yield TaskSeq.empty
yield TaskSeq.empty
yield TaskSeq.empty
yield TaskSeq.empty
yield taskSeq { do i <- i + 1 }
}
|> TaskSeq.concat
|> validateSequence
|> Task.map (fun () -> i |> should equal 3)
16 changes: 16 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ module TaskSeq =
yield! (ts :> taskSeq<'T>)
}

let append (source1: #taskSeq<'T>) (source2: #taskSeq<'T>) = taskSeq {
yield! (source1 :> IAsyncEnumerable<'T>)
yield! (source2 :> IAsyncEnumerable<'T>)
}

let appendSeq (source1: #taskSeq<'T>) (source2: #seq<'T>) = taskSeq {
yield! (source1 :> IAsyncEnumerable<'T>)
yield! (source2 :> seq<'T>)
}

let prependSeq (source1: #seq<'T>) (source2: #taskSeq<'T>) = taskSeq {
yield! (source1 :> seq<'T>)
yield! (source2 :> IAsyncEnumerable<'T>)
}

//
// iter/map/collect functions
//
Expand Down Expand Up @@ -222,6 +237,7 @@ module TaskSeq =
| Some result -> return result
| None -> return Internal.raiseEmptySeq ()
}

let tryItem index source = Internal.tryItem index source

let item index source = task {
Expand Down
33 changes: 33 additions & 0 deletions src/FSharp.Control.TaskSeq/TaskSeq.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,39 @@ module TaskSeq =
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T>

/// <summary>
/// Concatenates task sequences <paramref name="source1" /> and <paramref name="source2" /> in order as a single
/// task sequence.
/// </summary>
///
/// <param name="source1">The first input task sequence.</param>
/// <param name="source2">The second input task sequence.</param>
/// <returns>The resulting task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>

/// <summary>
/// Concatenates a task sequence <paramref name="source1" /> with a non-async F# <see cref="seq" /> in <paramref name="source2" />
/// and returns a single task sequence.
/// </summary>
///
/// <param name="source1">The input task sequence.</param>
/// <param name="source2">The input F# <see cref="seq" /> sequence.</param>
/// <returns>The resulting task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T>

/// <summary>
/// Concatenates a non-async F# <see cref="seq" /> in <paramref name="source1" /> with a task sequence in <paramref name="source2" />
/// and returns a single task sequence.
/// </summary>
///
/// <param name="source1">The input F# <see cref="seq" /> sequence.</param>
/// <param name="source2">The input task sequence.</param>
/// <returns>The resulting task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>

/// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
val toList: source: taskSeq<'T> -> 'T list

Expand Down