Skip to content

Commit b66463c

Browse files
authored
Merge pull request #217 from fsprojects/implement-where-whereasync
Implement `TaskSeq.where` and `TaskSeq.whereAsync`
2 parents c644f29 + 6311817 commit b66463c

File tree

7 files changed

+130
-50
lines changed

7 files changed

+130
-50
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ The _resumable state machine_ backing the `taskSeq` CE is now finished and _rest
225225

226226
We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided by `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help.
227227

228-
The following is the progress report:
228+
This is what has been implemented so far, is planned or skipped:
229229

230230
| Done | `Seq` | `TaskSeq` | Variants | Remarks |
231231
|------------------|--------------------|----------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -353,7 +353,7 @@ The following is the progress report:
353353
| ✅ [#76][] | | `tryTail` | | |
354354
| | `unfold` | `unfold` | `unfoldAsync` | |
355355
| | `updateAt` | `updateAt` | | |
356-
| | `where` | `where` | `whereAsync` | |
356+
| ✅ [#217][]| `where` | `where` | `whereAsync` | |
357357
| | `windowed` | `windowed` | | |
358358
| ✅ [#2][] | `zip` | `zip` | | |
359359
| | `zip3` | `zip3` | | |
@@ -551,6 +551,8 @@ module TaskSeq =
551551
val tryPick: chooser: ('T -> 'U option) -> source: TaskSeq<'T> -> Task<'U option>
552552
val tryPickAsync: chooser: ('T -> #Task<'U option>) -> source: TaskSeq<'T> -> Task<'U option>
553553
val tryTail: source: TaskSeq<'T> -> Task<TaskSeq<'T> option>
554+
val where: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
555+
val whereAsync: predicate: ('T -> #Task<bool>) -> source: TaskSeq<'T> -> TaskSeq<'T>
554556
val unbox<'U when 'U: struct> : source: TaskSeq<obj> -> TaskSeq<'U>
555557
val zip: source1: TaskSeq<'T> -> source2: TaskSeq<'U> -> TaskSeq<'T * 'U>
556558
```
@@ -600,6 +602,7 @@ module TaskSeq =
600602
[#126]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/126
601603
[#133]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/133
602604
[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209
605+
[#217]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/217
603606

604607
[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues
605608
[nuget]: https://www.nuget.org/packages/FSharp.Control.TaskSeq/

assets/nuget-package-readme.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,9 @@ let feedFromTwitter user pwd = taskSeq {
103103

104104
### `TaskSeq` module functions
105105

106-
We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided from `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help.
107-
108106
We are working hard on getting a full set of module functions on `TaskSeq` that can be used with `IAsyncEnumerable` sequences. Our guide is the set of F# `Seq` functions in F# Core and, where applicable, the functions provided by `AsyncSeq`. Each implemented function is documented through XML doc comments to provide the necessary context-sensitive help.
109107

110-
This is what was implemented, planned or skipped:
108+
This is what has been implemented so far, is planned or skipped:
111109

112110
| Done | `Seq` | `TaskSeq` | Variants | Remarks |
113111
|------------------|--------------------|----------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -235,7 +233,7 @@ This is what was implemented, planned or skipped:
235233
| &#x2705; [#76][] | | `tryTail` | | |
236234
| | `unfold` | `unfold` | `unfoldAsync` | |
237235
| | `updateAt` | `updateAt` | | |
238-
| | `where` | `where` | `whereAsync` | |
236+
| &#x2705; [#217][]| `where` | `where` | `whereAsync` | |
239237
| | `windowed` | `windowed` | | |
240238
| &#x2705; [#2][] | `zip` | `zip` | | |
241239
| | `zip3` | `zip3` | | |
@@ -308,4 +306,5 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l
308306
[#83]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/83
309307
[#90]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/90
310308
[#126]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/126
311-
[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209
309+
[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209
310+
[#217]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/217

release-notes.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ Release notes:
55
- new surface area functions, fixes #208:
66
* TaskSeq.take, TaskSeq.skip, #209
77
* TaskSeq.truncate, TaskSeq.drop, #209
8+
* TaskSeq.where, TaskSeq.whereAsync, #217
89

910
- Performance: less thread hops with 'StartImmediateAsTask' instead of 'StartAsTask', fixes #135
1011
- BINARY INCOMPATIBILITY: 'TaskSeq' module is now static members on 'TaskSeq<_>', fixes #184
11-
- DEPRECATIONS (warning FS0044):
12+
- DEPRECATIONS (warning FS0044):
1213
- type 'taskSeq<_>' is renamed to 'TaskSeq<_>', fixes #193
1314
- function 'ValueTask.ofIValueTaskSource` renamed to `ValueTask.ofSource`, fixes #193
1415
- function `ValueTask.FromResult` is renamed to `ValueTask.fromResult`, fixes #193
15-
16+
1617
0.4.0-alpha.1
1718
- fixes not calling Dispose for 'use!', 'use', or `finally` blocks #157 (by @bartelink)
1819
- BREAKING CHANGE: null args now raise ArgumentNullException instead of NullReferenceException, #127
1920
- adds `let!` and `do!` support for F#'s Async<'T>, #79, #114
2021
- adds TaskSeq.takeWhile, takeWhileAsync, takeWhileInclusive, takeWhileInclusiveAsync, #126 (by @bartelink)
2122
- adds AsyncSeq vs TaskSeq comparison chart, #131
2223
- removes release-notes.txt from file dependencies, but keep in the package, #138
23-
24+
2425
0.3.0
2526
- internal renames, improved doc comments, signature files for complex types, hide internal-only types, fixes #112.
2627
- adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110.

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

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,67 +10,108 @@ open FSharp.Control
1010
//
1111
// TaskSeq.filter
1212
// TaskSeq.filterAsync
13+
// TaskSeq.where
14+
// TaskSeq.whereAsync
1315
//
1416

1517

1618
module EmptySeq =
1719
[<Fact>]
18-
let ``Null source is invalid`` () =
20+
let ``TaskSeq-filter or where with null source raises`` () =
1921
assertNullArg
2022
<| fun () -> TaskSeq.filter (fun _ -> false) null
2123

2224
assertNullArg
2325
<| fun () -> TaskSeq.filterAsync (fun _ -> Task.fromResult false) null
2426

27+
assertNullArg
28+
<| fun () -> TaskSeq.where (fun _ -> false) null
29+
30+
assertNullArg
31+
<| fun () -> TaskSeq.whereAsync (fun _ -> Task.fromResult false) null
32+
2533

2634
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
27-
let ``TaskSeq-filter has no effect`` variant =
28-
Gen.getEmptyVariant variant
29-
|> TaskSeq.filter ((=) 12)
30-
|> TaskSeq.toListAsync
31-
|> Task.map (List.isEmpty >> should be True)
35+
let ``TaskSeq-filter or where has no effect`` variant = task {
36+
do!
37+
Gen.getEmptyVariant variant
38+
|> TaskSeq.filter ((=) 12)
39+
|> TaskSeq.toListAsync
40+
|> Task.map (List.isEmpty >> should be True)
41+
42+
do!
43+
Gen.getEmptyVariant variant
44+
|> TaskSeq.where ((=) 12)
45+
|> TaskSeq.toListAsync
46+
|> Task.map (List.isEmpty >> should be True)
47+
}
3248

3349
[<Theory; ClassData(typeof<TestEmptyVariants>)>]
34-
let ``TaskSeq-filterAsync has no effect`` variant =
35-
Gen.getEmptyVariant variant
36-
|> TaskSeq.filterAsync (fun x -> task { return x = 12 })
37-
|> TaskSeq.toListAsync
38-
|> Task.map (List.isEmpty >> should be True)
50+
let ``TaskSeq-filterAsync or whereAsync has no effect`` variant = task {
51+
do!
52+
Gen.getEmptyVariant variant
53+
|> TaskSeq.filterAsync (fun x -> task { return x = 12 })
54+
|> TaskSeq.toListAsync
55+
|> Task.map (List.isEmpty >> should be True)
56+
57+
do!
58+
Gen.getEmptyVariant variant
59+
|> TaskSeq.whereAsync (fun x -> task { return x = 12 })
60+
|> TaskSeq.toListAsync
61+
|> Task.map (List.isEmpty >> should be True)
62+
}
3963

4064
module Immutable =
4165
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
42-
let ``TaskSeq-filter filters correctly`` variant =
43-
Gen.getSeqImmutable variant
44-
|> TaskSeq.filter ((<=) 5) // greater than
45-
|> TaskSeq.map char
46-
|> TaskSeq.map ((+) '@')
47-
|> TaskSeq.toArrayAsync
48-
|> Task.map (String >> should equal "EFGHIJ")
66+
let ``TaskSeq-filter or where filters correctly`` variant = task {
67+
do!
68+
Gen.getSeqImmutable variant
69+
|> TaskSeq.filter ((<=) 5) // greater than
70+
|> verifyDigitsAsString "EFGHIJ"
71+
72+
do!
73+
Gen.getSeqImmutable variant
74+
|> TaskSeq.where ((>) 5) // greater than
75+
|> verifyDigitsAsString "ABCD"
76+
}
4977

5078
[<Theory; ClassData(typeof<TestImmTaskSeq>)>]
51-
let ``TaskSeq-filterAsync filters correctly`` variant =
52-
Gen.getSeqImmutable variant
53-
|> TaskSeq.filterAsync (fun x -> task { return x <= 5 })
54-
|> TaskSeq.map char
55-
|> TaskSeq.map ((+) '@')
56-
|> TaskSeq.toArrayAsync
57-
|> Task.map (String >> should equal "ABCDE")
79+
let ``TaskSeq-filterAsync or whereAsync filters correctly`` variant = task {
80+
do!
81+
Gen.getSeqImmutable variant
82+
|> TaskSeq.filterAsync (fun x -> task { return x <= 5 })
83+
|> verifyDigitsAsString "ABCDE"
84+
85+
do!
86+
Gen.getSeqImmutable variant
87+
|> TaskSeq.whereAsync (fun x -> task { return x > 5 })
88+
|> verifyDigitsAsString "FGHIJ"
89+
90+
}
5891

5992
module SideEffects =
6093
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
61-
let ``TaskSeq-filter filters correctly`` variant =
62-
Gen.getSeqWithSideEffect variant
63-
|> TaskSeq.filter ((<=) 5) // greater than
64-
|> TaskSeq.map char
65-
|> TaskSeq.map ((+) '@')
66-
|> TaskSeq.toArrayAsync
67-
|> Task.map (String >> should equal "EFGHIJ")
94+
let ``TaskSeq-filter filters correctly`` variant = task {
95+
do!
96+
Gen.getSeqWithSideEffect variant
97+
|> TaskSeq.filter ((<=) 5) // greater than or equal
98+
|> verifyDigitsAsString "EFGHIJ"
99+
100+
do!
101+
Gen.getSeqWithSideEffect variant
102+
|> TaskSeq.where ((>) 5) // less than
103+
|> verifyDigitsAsString "ABCD"
104+
}
68105

69106
[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
70-
let ``TaskSeq-filterAsync filters correctly`` variant =
71-
Gen.getSeqWithSideEffect variant
72-
|> TaskSeq.filterAsync (fun x -> task { return x <= 5 })
73-
|> TaskSeq.map char
74-
|> TaskSeq.map ((+) '@')
75-
|> TaskSeq.toArrayAsync
76-
|> Task.map (String >> should equal "ABCDE")
107+
let ``TaskSeq-filterAsync filters correctly`` variant = task {
108+
do!
109+
Gen.getSeqWithSideEffect variant
110+
|> TaskSeq.filterAsync (fun x -> task { return x <= 5 })
111+
|> verifyDigitsAsString "ABCDE"
112+
113+
do!
114+
Gen.getSeqWithSideEffect variant
115+
|> TaskSeq.whereAsync (fun x -> task { return x > 5 && x < 9 })
116+
|> verifyDigitsAsString "FGH"
117+
}

src/FSharp.Control.TaskSeq.Test/TestUtils.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ module TestUtils =
141141
|> TaskSeq.toArrayAsync
142142
|> Task.map (should equal [| 1..10 |])
143143

144+
/// Turns a sequence of numbers into a string, starting with A for '1'
145+
let verifyDigitsAsString expected =
146+
TaskSeq.map char
147+
>> TaskSeq.map ((+) '@')
148+
>> TaskSeq.toArrayAsync
149+
>> Task.map (String >> should equal expected)
150+
144151
/// Delays (no spin-wait!) between 20 and 70ms, assuming a 15.6ms resolution clock
145152
let longDelay () = task { do! Task.Delay(Random().Next(20, 70)) }
146153

src/FSharp.Control.TaskSeq/TaskSeq.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ type TaskSeq private () =
285285

286286
static member filter predicate source = Internal.filter (Predicate predicate) source
287287
static member filterAsync predicate source = Internal.filter (PredicateAsync predicate) source
288+
static member where predicate source = Internal.filter (Predicate predicate) source
289+
static member whereAsync predicate source = Internal.filter (PredicateAsync predicate) source
288290

289291
static member skip count source = Internal.skipOrTake Skip count source
290292
static member drop count source = Internal.skipOrTake Drop count source

src/FSharp.Control.TaskSeq/TaskSeq.fsi

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,34 @@ type TaskSeq =
725725
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
726726
static member filterAsync: predicate: ('T -> #Task<bool>) -> source: TaskSeq<'T> -> TaskSeq<'T>
727727

728+
/// <summary>
729+
/// Returns a new task sequence containing only the elements of the collection
730+
/// for which the given function <paramref name="predicate" /> returns <see cref="true" />.
731+
/// If <paramref name="predicate" /> is asynchronous, consider using <see cref="TaskSeq.whereAsync" />.
732+
///
733+
/// Alias for <see cref="TaskSeq.filter" />.
734+
/// </summary>
735+
///
736+
/// <param name="predicate">A function to test whether an item in the input sequence should be included in the output or not.</param>
737+
/// <param name="source">The input task sequence.</param>
738+
/// <returns>The resulting task sequence.</returns>
739+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
740+
static member where: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
741+
742+
/// <summary>
743+
/// Returns a new task sequence containing only the elements of the input sequence
744+
/// for which the given function <paramref name="predicate" /> returns <see cref="true" />.
745+
/// If <paramref name="predicate" /> is synchronous, consider using <see cref="TaskSeq.where" />.
746+
///
747+
/// Alias for <see cref="TaskSeq.filterAsync" />.
748+
/// </summary>
749+
///
750+
/// <param name="predicate">An asynchronous function to test whether an item in the input sequence should be included in the output or not.</param>
751+
/// <param name="source">The input task sequence.</param>
752+
/// <returns>The resulting task sequence.</returns>
753+
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
754+
static member whereAsync: predicate: ('T -> #Task<bool>) -> source: TaskSeq<'T> -> TaskSeq<'T>
755+
728756
/// <summary>
729757
/// Returns a task sequence that, when iterated, skips <paramref name="count" /> elements of the underlying
730758
/// sequence, and then yields the remainder. Raises an exception if there are not <paramref name="count" />
@@ -742,7 +770,6 @@ type TaskSeq =
742770
/// </exception>
743771
static member skip: count: int -> source: TaskSeq<'T> -> TaskSeq<'T>
744772

745-
746773
/// <summary>
747774
/// Returns a task sequence that, when iterated, drops at most <paramref name="count" /> elements of the
748775
/// underlying sequence, and then returns the remainder of the elements, if any.

0 commit comments

Comments
 (0)