Skip to content

Commit 3b28b83

Browse files
committed
Add implementation and tests for TaskSeq.append, appendSeq and prependSeq
1 parent 34e382d commit 3b28b83

File tree

5 files changed

+224
-1
lines changed

5 files changed

+224
-1
lines changed

src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Compile Include="AssemblyInfo.fs" />
1313
<Compile Include="Nunit.Extensions.fs" />
1414
<Compile Include="TestUtils.fs" />
15+
<Compile Include="TaskSeq.Append.Tests.fs" />
1516
<Compile Include="TaskSeq.Cast.Tests.fs" />
1617
<Compile Include="TaskSeq.Choose.Tests.fs" />
1718
<Compile Include="TaskSeq.Collect.Tests.fs" />
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
module TaskSeq.Tests.Append
2+
3+
open System
4+
5+
open Xunit
6+
open FsUnit.Xunit
7+
open FsToolkit.ErrorHandling
8+
9+
open FSharp.Control
10+
open System.Collections.Generic
11+
12+
//
13+
// TaskSeq.append
14+
// TaskSeq.appendSeq
15+
// TaskSeq.prependSeq
16+
//
17+
18+
let validateSequence ts =
19+
ts
20+
|> TaskSeq.toSeqCachedAsync
21+
|> Task.map (Seq.map string)
22+
|> Task.map (String.concat "")
23+
|> Task.map (should equal "1234567891012345678910")
24+
25+
module EmptySeq =
26+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
27+
let ``TaskSeq-append both args empty`` variant =
28+
Gen.getEmptyVariant variant
29+
|> TaskSeq.append (Gen.getEmptyVariant variant)
30+
|> verifyEmpty
31+
32+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
33+
let ``TaskSeq-appendSeq both args empty`` variant =
34+
Seq.empty
35+
|> TaskSeq.appendSeq (Gen.getEmptyVariant variant)
36+
|> verifyEmpty
37+
38+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
39+
let ``TaskSeq-prependSeq both args empty`` variant =
40+
Gen.getEmptyVariant variant
41+
|> TaskSeq.prependSeq Seq.empty
42+
|> verifyEmpty
43+
44+
module Immutable =
45+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
46+
let ``TaskSeq-append`` variant =
47+
Gen.getSeqImmutable variant
48+
|> TaskSeq.append (Gen.getSeqImmutable variant)
49+
|> validateSequence
50+
51+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
52+
let ``TaskSeq-appendSeq with a list`` variant =
53+
[ 1..10 ]
54+
|> TaskSeq.appendSeq (Gen.getSeqImmutable variant)
55+
|> validateSequence
56+
57+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
58+
let ``TaskSeq-appendSeq with an array`` variant =
59+
[| 1..10 |]
60+
|> TaskSeq.appendSeq (Gen.getSeqImmutable variant)
61+
|> validateSequence
62+
63+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
64+
let ``TaskSeq-prependSeq with a list`` variant =
65+
Gen.getSeqImmutable variant
66+
|> TaskSeq.prependSeq [ 1..10 ]
67+
|> validateSequence
68+
69+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
70+
let ``TaskSeq-prependSeq with an array`` variant =
71+
Gen.getSeqImmutable variant
72+
|> TaskSeq.prependSeq [| 1..10 |]
73+
|> validateSequence
74+
75+
module SideEffects =
76+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
77+
let ``TaskSeq-append consumes whole sequence once incl after-effects`` variant =
78+
let mutable i = 0
79+
80+
taskSeq {
81+
i <- i + 1
82+
yield! [ 1..10 ]
83+
i <- i + 1
84+
}
85+
|> TaskSeq.append (Gen.getSeqImmutable variant)
86+
|> validateSequence
87+
|> Task.map (fun () -> i |> should equal 2)
88+
89+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
90+
let ``TaskSeq-appendSeq consumes whole sequence once incl after-effects`` variant =
91+
let mutable i = 0
92+
93+
let ts = taskSeq {
94+
i <- i + 1
95+
yield! [ 1..10 ]
96+
i <- i + 1
97+
}
98+
99+
[| 1..10 |]
100+
|> TaskSeq.appendSeq ts
101+
|> validateSequence
102+
|> Task.map (fun () -> i |> should equal 2)
103+
104+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
105+
let ``TaskSeq-prependSeq consumes whole sequence once incl after-effects`` variant =
106+
let mutable i = 0
107+
108+
taskSeq {
109+
i <- i + 1
110+
yield! [ 1..10 ]
111+
i <- i + 1
112+
}
113+
|> TaskSeq.prependSeq [ 1..10 ]
114+
|> validateSequence
115+
|> Task.map (fun () -> i |> should equal 2)

src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,69 @@ module EmptySeq =
4141

4242
module Immutable =
4343
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
44-
let ``TaskSeq-concat with empty sequences`` variant =
44+
let ``TaskSeq-concat with three sequences of sequences`` variant =
45+
taskSeq {
46+
yield Gen.getSeqImmutable variant // not yield-bang!
47+
yield Gen.getSeqImmutable variant
48+
yield Gen.getSeqImmutable variant
49+
}
50+
|> TaskSeq.concat
51+
|> validateSequence
52+
53+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
54+
let ``TaskSeq-concat with three sequences of sequences and few empties`` variant =
55+
taskSeq {
56+
yield TaskSeq.empty
57+
yield Gen.getSeqImmutable variant // not yield-bang!
58+
yield TaskSeq.empty
59+
yield TaskSeq.empty
60+
yield Gen.getSeqImmutable variant
61+
yield TaskSeq.empty
62+
yield Gen.getSeqImmutable variant
63+
yield TaskSeq.empty
64+
yield TaskSeq.empty
65+
yield TaskSeq.empty
66+
yield TaskSeq.empty
67+
}
68+
|> TaskSeq.concat
69+
|> validateSequence
70+
71+
module SideEffect =
72+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
73+
let ``TaskSeq-concat consumes until the end, including side-effects`` variant =
74+
let mutable i = 0
75+
76+
taskSeq {
77+
yield Gen.getSeqImmutable variant // not yield-bang!
78+
yield Gen.getSeqImmutable variant
79+
80+
yield taskSeq {
81+
yield! [ 1..10 ]
82+
i <- i + 1
83+
}
84+
}
85+
|> TaskSeq.concat
86+
|> validateSequence
87+
|> Task.map (fun () -> i |> should equal 1)
88+
89+
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
90+
let ``TaskSeq-concat consumes side effects in empty sequences`` variant =
91+
let mutable i = 0
92+
4593
taskSeq {
94+
yield taskSeq { do i <- i + 1 }
4695
yield Gen.getSeqImmutable variant // not yield-bang!
96+
yield TaskSeq.empty
97+
yield taskSeq { do i <- i + 1 }
4798
yield Gen.getSeqImmutable variant
99+
yield TaskSeq.empty
48100
yield Gen.getSeqImmutable variant
101+
yield TaskSeq.empty
102+
yield TaskSeq.empty
103+
yield TaskSeq.empty
104+
yield TaskSeq.empty
105+
yield taskSeq { do i <- i + 1 }
49106
}
50107
|> TaskSeq.concat
51108
|> validateSequence
109+
|> Task.map (fun () -> i |> should equal 3)

src/FSharp.Control.TaskSeq/TaskSeq.fs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,21 @@ module TaskSeq =
169169
yield! (ts :> taskSeq<'T>)
170170
}
171171

172+
let append (source1: #taskSeq<'T>) (source2: #taskSeq<'T>) = taskSeq {
173+
yield! (source1 :> IAsyncEnumerable<'T>)
174+
yield! (source2 :> IAsyncEnumerable<'T>)
175+
}
176+
177+
let appendSeq (source1: #taskSeq<'T>) (source2: #seq<'T>) = taskSeq {
178+
yield! (source1 :> IAsyncEnumerable<'T>)
179+
yield! (source2 :> seq<'T>)
180+
}
181+
182+
let prependSeq (source1: #seq<'T>) (source2: #taskSeq<'T>) = taskSeq {
183+
yield! (source1 :> seq<'T>)
184+
yield! (source2 :> IAsyncEnumerable<'T>)
185+
}
186+
172187
//
173188
// iter/map/collect functions
174189
//
@@ -222,6 +237,7 @@ module TaskSeq =
222237
| Some result -> return result
223238
| None -> return Internal.raiseEmptySeq ()
224239
}
240+
225241
let tryItem index source = Internal.tryItem index source
226242

227243
let item index source = task {

src/FSharp.Control.TaskSeq/TaskSeq.fsi

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,39 @@ module TaskSeq =
107107
/// <exception cref="T:ArgumentNullException">Thrown when the input sequence is null.</exception>
108108
val concat: sources: taskSeq<#taskSeq<'T>> -> taskSeq<'T>
109109

110+
/// <summary>
111+
/// Concatenates task sequences <paramref name="source1" /> and <paramref name="source2" /> in order as a single
112+
/// task sequence.
113+
/// </summary>
114+
///
115+
/// <param name="source1">The first input task sequence.</param>
116+
/// <param name="source2">The second input task sequence.</param>
117+
/// <returns>The resulting task sequence.</returns>
118+
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
119+
val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
120+
121+
/// <summary>
122+
/// Concatenates a task sequence <paramref name="source1" /> with a non-async F# <see cref="seq" /> in <paramref name="source2" />
123+
/// and returns a single task sequence.
124+
/// </summary>
125+
///
126+
/// <param name="source1">The input task sequence.</param>
127+
/// <param name="source2">The input F# <see cref="seq" /> sequence.</param>
128+
/// <returns>The resulting task sequence.</returns>
129+
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
130+
val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T>
131+
132+
/// <summary>
133+
/// Concatenates a non-async F# <see cref="seq" /> in <paramref name="source1" /> with a task sequence in <paramref name="source2" />
134+
/// and returns a single task sequence.
135+
/// </summary>
136+
///
137+
/// <param name="source1">The input F# <see cref="seq" /> sequence.</param>
138+
/// <param name="source2">The input task sequence.</param>
139+
/// <returns>The resulting task sequence.</returns>
140+
/// <exception cref="T:ArgumentNullException">Thrown when either of the input sequences is null.</exception>
141+
val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
142+
110143
/// Returns taskSeq as an array. This function is blocking until the sequence is exhausted and will properly dispose of the resources.
111144
val toList: source: taskSeq<'T> -> 'T list
112145

0 commit comments

Comments
 (0)