Skip to content

Commit 2fc7d94

Browse files
authored
Merge pull request #237 from fsprojects/implement-concat-overloads
Add `TaskSeq.concat` overloads for seq, list, array, resizearray
2 parents cedbb32 + d0fe3f1 commit 2fc7d94

File tree

3 files changed

+270
-16
lines changed

3 files changed

+270
-16
lines changed

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

Lines changed: 191 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module TaskSeq.Tests.Concat
22

3+
open System
34
open System.Collections.Generic
45

56
open Xunit
@@ -8,7 +9,11 @@ open FsUnit.Xunit
89
open FSharp.Control
910

1011
//
11-
// TaskSeq.concat
12+
// TaskSeq.concat - of task seqs
13+
// TaskSeq.concat - of seqs
14+
// TaskSeq.concat - of lists
15+
// TaskSeq.concat - of arrays
16+
// TaskSeq.concat - of resizable arrays
1217
//
1318

1419
let validateSequence ts =
@@ -20,31 +25,126 @@ let validateSequence ts =
2025

2126
module EmptySeq =
2227
[<Fact>]
23-
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.concat null
28+
let ``Null source is invalid (taskseq)`` () =
29+
assertNullArg
30+
<| fun () -> TaskSeq.concat (null: TaskSeq<TaskSeq<_>>)
31+
32+
[<Fact>]
33+
let ``Null source is invalid (seq)`` () =
34+
assertNullArg
35+
<| fun () -> TaskSeq.concat (null: TaskSeq<seq<_>>)
36+
37+
[<Fact>]
38+
let ``Null source is invalid (array)`` () =
39+
assertNullArg
40+
<| fun () -> TaskSeq.concat (null: TaskSeq<array<_>>)
41+
42+
[<Fact>]
43+
let ``Null source is invalid (list)`` () =
44+
assertNullArg
45+
<| fun () -> TaskSeq.concat (null: TaskSeq<list<_>>)
46+
47+
[<Fact>]
48+
let ``Null source is invalid (resizarray)`` () =
49+
assertNullArg
50+
<| fun () -> TaskSeq.concat (null: TaskSeq<ResizeArray<_>>)
2451

2552
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
26-
let ``TaskSeq-concat with empty sequences`` variant =
53+
let ``TaskSeq-concat with nested empty task sequences`` variant =
2754
taskSeq {
28-
yield Gen.getEmptyVariant variant // not yield-bang!
2955
yield Gen.getEmptyVariant variant
3056
yield Gen.getEmptyVariant variant
57+
yield Gen.getEmptyVariant variant
58+
}
59+
|> TaskSeq.concat
60+
|> verifyEmpty
61+
62+
[<Fact>]
63+
let ``TaskSeq-concat with nested empty sequences`` () =
64+
taskSeq {
65+
yield Seq.empty<string>
66+
yield Seq.empty<string>
67+
yield Seq.empty<string>
68+
}
69+
|> TaskSeq.concat
70+
|> verifyEmpty
71+
72+
[<Fact>]
73+
let ``TaskSeq-concat with nested empty arrays`` () =
74+
taskSeq {
75+
yield Array.empty<int>
76+
yield Array.empty<int>
77+
yield Array.empty<int>
78+
}
79+
|> TaskSeq.concat
80+
|> verifyEmpty
81+
82+
[<Fact>]
83+
let ``TaskSeq-concat with nested empty lists`` () =
84+
taskSeq {
85+
yield List.empty<Guid>
86+
yield List.empty<Guid>
87+
yield List.empty<Guid>
3188
}
3289
|> TaskSeq.concat
3390
|> verifyEmpty
3491

92+
[<Fact>]
93+
let ``TaskSeq-concat with multiple nested empty resizable arrays`` () =
94+
taskSeq {
95+
yield ResizeArray(List.empty<byte>)
96+
yield ResizeArray(List.empty<byte>)
97+
yield ResizeArray(List.empty<byte>)
98+
}
99+
|> TaskSeq.concat
100+
|> verifyEmpty
101+
102+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
103+
let ``TaskSeq-concat with empty source (taskseq)`` variant =
104+
Gen.getEmptyVariant variant
105+
|> TaskSeq.box
106+
|> TaskSeq.cast<IAsyncEnumerable<int>> // task seq is empty so this should not raise
107+
|> TaskSeq.concat
108+
|> verifyEmpty
109+
110+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
111+
let ``TaskSeq-concat with empty source (seq)`` variant =
112+
Gen.getEmptyVariant variant
113+
|> TaskSeq.box
114+
|> TaskSeq.cast<int seq> // task seq is empty so this should not raise
115+
|> TaskSeq.concat
116+
|> verifyEmpty
117+
35118
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
36-
let ``TaskSeq-concat with top sequence empty`` variant =
119+
let ``TaskSeq-concat with empty source (list)`` variant =
37120
Gen.getEmptyVariant variant
38121
|> TaskSeq.box
39-
|> TaskSeq.cast<IAsyncEnumerable<int>> // casting an int to an enumerable, LOL!
122+
|> TaskSeq.cast<int list> // task seq is empty so this should not raise
40123
|> TaskSeq.concat
41124
|> verifyEmpty
42125

126+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
127+
let ``TaskSeq-concat with empty source (array)`` variant =
128+
Gen.getEmptyVariant variant
129+
|> TaskSeq.box
130+
|> TaskSeq.cast<int[]> // task seq is empty so this should not raise
131+
|> TaskSeq.concat
132+
|> verifyEmpty
133+
134+
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
135+
let ``TaskSeq-concat with empty source (resizearray)`` variant =
136+
Gen.getEmptyVariant variant
137+
|> TaskSeq.box
138+
|> TaskSeq.cast<ResizeArray<int>> // task seq is empty so this should not raise
139+
|> TaskSeq.concat
140+
|> verifyEmpty
141+
142+
43143
module Immutable =
44144
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
45145
let ``TaskSeq-concat with three sequences of sequences`` variant =
46146
taskSeq {
47-
yield Gen.getSeqImmutable variant // not yield-bang!
147+
yield Gen.getSeqImmutable variant
48148
yield Gen.getSeqImmutable variant
49149
yield Gen.getSeqImmutable variant
50150
}
@@ -55,7 +155,7 @@ module Immutable =
55155
let ``TaskSeq-concat with three sequences of sequences and few empties`` variant =
56156
taskSeq {
57157
yield TaskSeq.empty
58-
yield Gen.getSeqImmutable variant // not yield-bang!
158+
yield Gen.getSeqImmutable variant
59159
yield TaskSeq.empty
60160
yield TaskSeq.empty
61161
yield Gen.getSeqImmutable variant
@@ -69,23 +169,100 @@ module Immutable =
69169
|> TaskSeq.concat
70170
|> validateSequence
71171

72-
module SideEffect =
73172
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
74-
let ``TaskSeq-concat consumes until the end, including side-effects`` variant =
173+
let ``TaskSeq-concat throws when one of inner task sequence is null`` variant =
174+
fun () ->
175+
taskSeq {
176+
yield Gen.getSeqImmutable variant
177+
yield TaskSeq.empty
178+
yield null
179+
}
180+
|> TaskSeq.concat
181+
|> consumeTaskSeq
182+
|> should throwAsyncExact typeof<NullReferenceException>
183+
184+
module SideEffect =
185+
[<Fact>]
186+
let ``TaskSeq-concat executes side effects of nested (taskseq)`` () =
75187
let mutable i = 0
76188

77189
taskSeq {
78-
yield Gen.getSeqImmutable variant // not yield-bang!
79-
yield Gen.getSeqImmutable variant
190+
yield Gen.getSeqImmutable SeqImmutable.ThreadSpinWait
191+
yield Gen.getSeqImmutable SeqImmutable.ThreadSpinWait
80192

81193
yield taskSeq {
82194
yield! [ 1..10 ]
83195
i <- i + 1
84196
}
85197
}
86198
|> TaskSeq.concat
87-
|> validateSequence
88-
|> Task.map (fun () -> i |> should equal 1)
199+
|> TaskSeq.last // consume
200+
|> Task.map (fun _ -> i |> should equal 1)
201+
202+
[<Fact>]
203+
let ``TaskSeq-concat executes side effects of nested (seq)`` () =
204+
let mutable i = 0
205+
206+
taskSeq {
207+
yield seq { 1..10 }
208+
yield seq { 1..10 }
209+
210+
yield seq {
211+
yield! [ 1..10 ]
212+
i <- i + 1
213+
}
214+
}
215+
|> TaskSeq.concat
216+
|> TaskSeq.last // consume
217+
|> Task.map (fun _ -> i |> should equal 1)
218+
219+
[<Fact>]
220+
let ``TaskSeq-concat executes side effects of nested (array)`` () =
221+
let mutable i = 0
222+
223+
taskSeq {
224+
yield [| 1..10 |]
225+
yield [| 1..10 |]
226+
227+
yield [| yield! [ 1..10 ]; i <- i + 1 |]
228+
}
229+
|> TaskSeq.concat
230+
|> TaskSeq.last // consume
231+
|> Task.map (fun _ -> i |> should equal 1)
232+
233+
[<Fact>]
234+
let ``TaskSeq-concat executes side effects of nested (list)`` () =
235+
let mutable i = 0
236+
237+
taskSeq {
238+
yield [ 1..10 ]
239+
yield [ 1..10 ]
240+
241+
yield [ yield! [ 1..10 ]; i <- i + 1 ]
242+
}
243+
|> TaskSeq.concat
244+
|> TaskSeq.last // consume
245+
|> Task.map (fun _ -> i |> should equal 1)
246+
247+
[<Fact>]
248+
let ``TaskSeq-concat executes side effects of nested (resizearray)`` () =
249+
let mutable i = 0
250+
251+
taskSeq {
252+
yield ResizeArray { 1..10 }
253+
yield ResizeArray { 1..10 }
254+
255+
yield
256+
ResizeArray(
257+
seq {
258+
yield! [ 1..10 ]
259+
i <- i + 1
260+
}
261+
)
262+
}
263+
|> TaskSeq.concat
264+
|> TaskSeq.last // consume
265+
|> Task.map (fun _ -> i |> should equal 1)
89266

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

src/FSharp.Control.TaskSeq/TaskSeq.fs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,42 @@ type TaskSeq private () =
191191
yield! (ts :> TaskSeq<'T>)
192192
}
193193

194+
static member concat(sources: TaskSeq<'T seq>) = // NOTE: we cannot use flex types on two overloads
195+
Internal.checkNonNull (nameof sources) sources
196+
197+
taskSeq {
198+
for ts in sources do
199+
// no null-check of inner seqs, similar to seq
200+
yield! ts
201+
}
202+
203+
static member concat(sources: TaskSeq<'T[]>) =
204+
Internal.checkNonNull (nameof sources) sources
205+
206+
taskSeq {
207+
for ts in sources do
208+
// no null-check of inner arrays, similar to seq
209+
yield! ts
210+
}
211+
212+
static member concat(sources: TaskSeq<'T list>) =
213+
Internal.checkNonNull (nameof sources) sources
214+
215+
taskSeq {
216+
for ts in sources do
217+
// no null-check of inner lists, similar to seq
218+
yield! ts
219+
}
220+
221+
static member concat(sources: TaskSeq<ResizeArray<'T>>) =
222+
Internal.checkNonNull (nameof sources) sources
223+
224+
taskSeq {
225+
for ts in sources do
226+
// no null-check of inner resize arrays, similar to seq
227+
yield! ts
228+
}
229+
194230
static member append (source1: TaskSeq<'T>) (source2: TaskSeq<'T>) =
195231
Internal.checkNonNull (nameof source1) source1
196232
Internal.checkNonNull (nameof source2) source2

src/FSharp.Control.TaskSeq/TaskSeq.fsi

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,55 @@ type TaskSeq =
215215

216216
/// <summary>
217217
/// Combines the given task sequence of task sequences and concatenates them end-to-end, to form a
218-
/// new flattened, single task sequence. Each task sequence is awaited item by item, before the next is iterated.
218+
/// new flattened, single task sequence, like <paramref name="TaskSeq.collect id"/>. Each task sequence is
219+
/// awaited and consumed in full, before the next one is iterated.
219220
/// </summary>
220221
///
221222
/// <param name="sources">The input task-sequence-of-task-sequences.</param>
222-
/// <returns>The resulting task sequence.</returns>
223+
/// <returns>The resulting, flattened task sequence.</returns>
223224
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence of task sequences is null.</exception>
224225
static member concat: sources: TaskSeq<#TaskSeq<'T>> -> TaskSeq<'T>
225226

227+
/// <summary>
228+
/// Combines the given task sequence of sequences and concatenates them end-to-end, to form a
229+
/// new flattened, single task sequence.
230+
/// </summary>
231+
///
232+
/// <param name="sources">The input task sequence of sequences.</param>
233+
/// <returns>The resulting, flattened task sequence.</returns>
234+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence of task sequences is null.</exception>
235+
static member concat: sources: TaskSeq<'T seq> -> TaskSeq<'T>
236+
237+
/// <summary>
238+
/// Combines the given task sequence of arrays and concatenates them end-to-end, to form a
239+
/// new flattened, single task sequence.
240+
/// </summary>
241+
///
242+
/// <param name="sources">The input task sequence of arrays.</param>
243+
/// <returns>The resulting, flattened task sequence.</returns>
244+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence of task sequences is null.</exception>
245+
static member concat: sources: TaskSeq<'T[]> -> TaskSeq<'T>
246+
247+
/// <summary>
248+
/// Combines the given task sequence of lists and concatenates them end-to-end, to form a
249+
/// new flattened, single task sequence.
250+
/// </summary>
251+
///
252+
/// <param name="sources">The input task sequence of lists.</param>
253+
/// <returns>The resulting, flattened task sequence.</returns>
254+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence of task sequences is null.</exception>
255+
static member concat: sources: TaskSeq<'T list> -> TaskSeq<'T>
256+
257+
/// <summary>
258+
/// Combines the given task sequence of resizable arrays and concatenates them end-to-end, to form a
259+
/// new flattened, single task sequence.
260+
/// </summary>
261+
///
262+
/// <param name="sources">The input task sequence of resizable arrays.</param>
263+
/// <returns>The resulting, flattened task sequence.</returns>
264+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence of task sequences is null.</exception>
265+
static member concat: sources: TaskSeq<ResizeArray<'T>> -> TaskSeq<'T>
266+
226267
/// <summary>
227268
/// Concatenates task sequences <paramref name="source1" /> and <paramref name="source2" /> in order as a single
228269
/// task sequence.

0 commit comments

Comments
 (0)