diff --git a/README.md b/README.md
index d8b23491..a3c499e6 100644
--- a/README.md
+++ b/README.md
@@ -319,8 +319,8 @@ This is what has been implemented so far, is planned or skipped:
| ✅ [#90][] | `singleton` | `singleton` | | |
| ✅ [#209][]| `skip` | `skip` | | |
| ✅ [#209][]| | `drop` | | |
-| | `skipWhile` | `skipWhile` | `skipWhileAsync` | |
-| | | `skipWhileInclusive` | `skipWhileInclusiveAsync` | |
+| ✅ [#219][]| `skipWhile` | `skipWhile` | `skipWhileAsync` | |
+| ✅ [#219][]| | `skipWhileInclusive` | `skipWhileInclusiveAsync` | |
| ❓ | `sort` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
| ❓ | `sortBy` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
| ❓ | `sortByAscending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
@@ -603,6 +603,7 @@ module TaskSeq =
[#133]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/133
[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209
[#217]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/217
+[#219]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/219
[issues]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues
[nuget]: https://www.nuget.org/packages/FSharp.Control.TaskSeq/
diff --git a/assets/nuget-package-readme.md b/assets/nuget-package-readme.md
index 1a7651e6..1e1a7f84 100644
--- a/assets/nuget-package-readme.md
+++ b/assets/nuget-package-readme.md
@@ -199,8 +199,8 @@ This is what has been implemented so far, is planned or skipped:
| ✅ [#90][] | `singleton` | `singleton` | | |
| ✅ [#209][]| `skip` | `skip` | | |
| ✅ [#209][]| | `drop` | | |
-| | `skipWhile` | `skipWhile` | `skipWhileAsync` | |
-| | | `skipWhileInclusive` | `skipWhileInclusiveAsync` | |
+| ✅ [#219][]| `skipWhile` | `skipWhile` | `skipWhileAsync` | |
+| ✅ [#219][]| | `skipWhileInclusive` | `skipWhileInclusiveAsync` | |
| ❓ | `sort` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
| ❓ | `sortBy` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
| ❓ | `sortByAscending` | | | [note #1](#note1 "These functions require a form of pre-materializing through 'TaskSeq.cache', similar to the approach taken in the corresponding 'Seq' functions. It doesn't make much sense to have a cached async sequence. However, 'AsyncSeq' does implement these, so we'll probably do so eventually as well.") |
@@ -308,3 +308,4 @@ _The motivation for `readOnly` in `Seq` is that a cast from a mutable array or l
[#126]: https://github.com/fsprojects/FSharp.Control.TaskSeq/pull/126
[#209]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/209
[#217]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/217
+[#219]: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/219
diff --git a/release-notes.txt b/release-notes.txt
index 06930db4..74096d35 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -3,9 +3,10 @@ Release notes:
0.4.x (unreleased)
- overhaul all doc comments, add exceptions, improve IDE quick-info experience, #136
- new surface area functions, fixes #208:
- * TaskSeq.take, TaskSeq.skip, #209
- * TaskSeq.truncate, TaskSeq.drop, #209
- * TaskSeq.where, TaskSeq.whereAsync, #217
+ * TaskSeq.take, skip, #209
+ * TaskSeq.truncate, drop, #209
+ * TaskSeq.where, whereAsync, #217
+ * TaskSeq.skipWhile, skipWhileInclusive, skipWhileAsync, skipWhileInclusiveAsync, #219
- Performance: less thread hops with 'StartImmediateAsTask' instead of 'StartAsTask', fixes #135
- BINARY INCOMPATIBILITY: 'TaskSeq' module is now static members on 'TaskSeq<_>', fixes #184
diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
index 16ba1b70..3536363d 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -36,6 +36,7 @@
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs
new file mode 100644
index 00000000..b0bf214e
--- /dev/null
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.SkipWhile.Tests.fs
@@ -0,0 +1,322 @@
+module TaskSeq.Tests.skipWhile
+
+open System
+
+open Xunit
+open FsUnit.Xunit
+
+open FSharp.Control
+
+//
+// TaskSeq.skipWhile
+// TaskSeq.skipWhileAsync
+// TaskSeq.skipWhileInclusive
+// TaskSeq.skipWhileInclusiveAsync
+//
+
+exception SideEffectPastEnd of string
+
+
+module EmptySeq =
+
+ // TaskSeq-skipWhile+A stands for:
+ // skipWhile + skipWhileAsync etc.
+
+ [)>]
+ let ``TaskSeq-skipWhile+A has no effect`` variant = task {
+ do!
+ Gen.getEmptyVariant variant
+ |> TaskSeq.skipWhile ((=) 12)
+ |> verifyEmpty
+
+ do!
+ Gen.getEmptyVariant variant
+ |> TaskSeq.skipWhileAsync ((=) 12 >> Task.fromResult)
+ |> verifyEmpty
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusive+A has no effect`` variant = task {
+ do!
+ Gen.getEmptyVariant variant
+ |> TaskSeq.skipWhileInclusive ((=) 12)
+ |> verifyEmpty
+
+ do!
+ Gen.getEmptyVariant variant
+ |> TaskSeq.skipWhileInclusiveAsync ((=) 12 >> Task.fromResult)
+ |> verifyEmpty
+ }
+
+module Immutable =
+
+ // TaskSeq-skipWhile+A stands for:
+ // skipWhile + skipWhileAsync etc.
+
+ [)>]
+ let ``TaskSeq-skipWhile+A filters correctly`` variant = task {
+ // truth table for f(x) = x < 5
+ // 1 2 3 4 5 6 7 8 9 10
+ // T T T T F F F F F F (stops at first F)
+ // x x x x _ _ _ _ _ _ (skips exclusive)
+ // A B C D E F G H I J
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhile (fun x -> x < 5)
+ |> verifyDigitsAsString "EFGHIJ"
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileAsync (fun x -> task { return x < 5 })
+ |> verifyDigitsAsString "EFGHIJ"
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhile+A does not skip first item when false`` variant = task {
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhile ((=) 0)
+ |> verifyDigitsAsString "ABCDEFGHIJ" // all 10 remain!
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileAsync ((=) 0 >> Task.fromResult)
+ |> verifyDigitsAsString "ABCDEFGHIJ" // all 10 remain!
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusive+A filters correctly`` variant = task {
+ // truth table for f(x) = x < 5
+ // 1 2 3 4 5 6 7 8 9 10
+ // T T T T F F F F F F (stops at first F)
+ // x x x x x _ _ _ _ _ (skips inclusively)
+ // A B C D E F G H I J
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusive (fun x -> x < 5)
+ |> verifyDigitsAsString "FGHIJ" // last 4
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 })
+ |> verifyDigitsAsString "FGHIJ"
+ }
+
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusive+A returns the empty sequence if always true`` variant = task {
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusive (fun x -> x > -1) // always true
+ |> verifyEmpty
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x > -1))
+ |> verifyEmpty
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusive+A always skips at least the first item`` variant = task {
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusive ((=) 0)
+ |> verifyDigitsAsString "BCDEFGHIJ"
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.skipWhileInclusiveAsync ((=) 0 >> Task.fromResult)
+ |> verifyDigitsAsString "BCDEFGHIJ"
+ }
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-skipWhile+A filters correctly`` variant = task {
+ // truth table for f(x) = x < 6
+ // 1 2 3 4 5 6 7 8 9 10
+ // T T T T T F F F F F (stops at first F)
+ // x x x x x _ _ _ _ _ (skips exclusively)
+ // A B C D E F G H I J
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.skipWhile (fun x -> x < 6)
+ |> verifyDigitsAsString "FGHIJ"
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.skipWhileAsync (fun x -> task { return x < 6 })
+ |> verifyDigitsAsString "FGHIJ"
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusive+A filters correctly`` variant = task {
+ // truth table for f(x) = x < 6
+ // 1 2 3 4 5 6 7 8 9 10
+ // T T T T T F F F F F (stops at first F)
+ // x x x x x x _ _ _ _ (skips inclusively)
+ // A B C D E F G H I J
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.skipWhileInclusive (fun x -> x < 6)
+ |> verifyDigitsAsString "GHIJ"
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 6 })
+ |> verifyDigitsAsString "GHIJ"
+ }
+
+ []
+ let ``TaskSeq-skipWhile and variants prove it reads the entire input stream`` () =
+
+ let mutable x = 42
+
+ let items = taskSeq {
+ yield x
+ yield x * 2
+ x <- x + 1 // we are proving we ALWAYS get here
+ }
+
+ // this needs to be lifted from the task or it raises the infamous
+ // warning FS3511 on CI: This state machine is not statically compilable
+ let testSkipper skipper expected = task {
+ let! first = items |> skipper |> TaskSeq.toArrayAsync
+ return first |> should equal expected
+ }
+
+ task {
+ do! testSkipper (TaskSeq.skipWhile ((=) 42)) [| 84 |]
+ x |> should equal 43
+
+ do! testSkipper (TaskSeq.skipWhileInclusive ((=) 43)) [||]
+ x |> should equal 44
+
+ do! testSkipper (TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x = 44))) [| 88 |]
+ x |> should equal 45
+
+ do! testSkipper (TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x = 45))) [||]
+ x |> should equal 46
+ }
+
+ []
+ let ``TaskSeq-skipWhile and variants prove side effects are properly executed`` () =
+ let mutable x = 41
+
+ let items = taskSeq {
+ x <- x + 1
+ yield x
+ x <- x + 2
+ yield x * 2
+ x <- x + 200 // as previously proven, we should ALWAYS trigger this
+ }
+
+ // this needs to be lifted from the task or it raises the infamous
+ // warning FS3511 on CI: This state machine is not statically compilable
+ let testSkipper skipper expected = task {
+ let! first = items |> skipper |> TaskSeq.toArrayAsync
+ return first |> should equal expected
+ }
+
+ task {
+ do! testSkipper (TaskSeq.skipWhile ((=) 42)) [| 88 |]
+ x |> should equal 244
+
+ do! testSkipper (TaskSeq.skipWhileInclusive ((=) 245)) [||]
+ x |> should equal 447
+
+ do! testSkipper (TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x = 448))) [| 900 |]
+ x |> should equal 650
+
+ do! testSkipper (TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x = 651))) [||]
+ x |> should equal 853
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhile consumes the prefix of a longer sequence, with mutation`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let! first =
+ TaskSeq.skipWhile (fun x -> x < 5) ts
+ |> TaskSeq.toArrayAsync
+
+ let expected = [| 5..10 |]
+ first |> should equal expected
+
+ // side effect, reiterating causes it to resume from where we left it (minus the failing item)
+ // which means the original sequence has now changed due to the side effect
+ let! repeat =
+ TaskSeq.skipWhile (fun x -> x < 5) ts
+ |> TaskSeq.toArrayAsync
+
+ repeat |> should not' (equal expected)
+ }
+
+ [)>]
+ let ``TaskSeq-skipWhileInclusiveAsync consumes the prefix for a longer sequence, with mutation`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ let! first =
+ TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 }) ts
+ |> TaskSeq.toArrayAsync
+
+ let expected = [| 6..10 |]
+ first |> should equal expected
+
+ // side effect, reiterating causes it to resume from where we left it (minus the failing item)
+ // which means the original sequence has now changed due to the side effect
+ let! repeat =
+ TaskSeq.skipWhileInclusiveAsync (fun x -> task { return x < 5 }) ts
+ |> TaskSeq.toArrayAsync
+
+ repeat |> should not' (equal expected)
+ }
+
+module Other =
+ []
+ let ``TaskSeq-skipWhileXXX should include all items after predicate fails`` () = task {
+ do!
+ [ 1; 2; 2; 3; 3; 2; 1 ]
+ |> TaskSeq.ofSeq
+ |> TaskSeq.skipWhile (fun x -> x <= 2)
+ |> verifyDigitsAsString "CCBA"
+
+ do!
+ [ 1; 2; 2; 3; 3; 2; 1 ]
+ |> TaskSeq.ofSeq
+ |> TaskSeq.skipWhileInclusive (fun x -> x <= 2)
+ |> verifyDigitsAsString "CBA"
+
+ do!
+ [ 1; 2; 2; 3; 3; 2; 1 ]
+ |> TaskSeq.ofSeq
+ |> TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x <= 2))
+ |> verifyDigitsAsString "CCBA"
+
+ do!
+ [ 1; 2; 2; 3; 3; 2; 1 ]
+ |> TaskSeq.ofSeq
+ |> TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x <= 2))
+ |> verifyDigitsAsString "CBA"
+ }
+
+ []
+ let ``TaskSeq-skipWhileXXX stops consuming after predicate fails`` () =
+ let testSkipper skipper =
+ fun () ->
+ seq {
+ yield! [ 1; 2; 2; 3; 3 ]
+ yield SideEffectPastEnd "Too far" |> raise
+ }
+ |> TaskSeq.ofSeq
+ |> skipper
+ |> consumeTaskSeq
+ |> should throwAsyncExact typeof
+
+ testSkipper (TaskSeq.skipWhile (fun x -> x <= 2))
+ testSkipper (TaskSeq.skipWhileInclusive (fun x -> x <= 2))
+ testSkipper (TaskSeq.skipWhileAsync (fun x -> Task.fromResult (x <= 2)))
+ testSkipper (TaskSeq.skipWhileInclusiveAsync (fun x -> Task.fromResult (x <= 2)))
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TakeWhile.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TakeWhile.Tests.fs
index a68e29ea..81fc3bf4 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.TakeWhile.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.TakeWhile.Tests.fs
@@ -39,6 +39,10 @@ module With =
res
module EmptySeq =
+
+ // TaskSeq-takeWhile+A stands for:
+ // takeWhile + takeWhileAsync etc.
+
[)>]
let ``TaskSeq-takeWhile+A has no effect`` variant = task {
do!
@@ -124,23 +128,37 @@ module Immutable =
module SideEffects =
[)>]
- let ``TaskSeq-takeWhile filters correctly`` variant =
- Gen.getSeqWithSideEffect variant
- |> TaskSeq.takeWhile condWithGuard
- |> verifyDigitsAsString "ABCDE"
+ let ``TaskSeq-takeWhile+A filters correctly`` variant = task {
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.takeWhile condWithGuard
+ |> verifyDigitsAsString "ABCDE"
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.takeWhileAsync (fun x -> task { return condWithGuard x })
+ |> verifyDigitsAsString "ABCDE"
+ }
[)>]
- let ``TaskSeq-takeWhileAsync filters correctly`` variant =
- Gen.getSeqWithSideEffect variant
- |> TaskSeq.takeWhileAsync (fun x -> task { return condWithGuard x })
- |> verifyDigitsAsString "ABCDE"
+ let ``TaskSeq-takeWhileInclusive+A filters correctly`` variant = task {
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.takeWhileInclusive condWithGuard
+ |> verifyDigitsAsString "ABCDEF"
+
+ do!
+ Gen.getSeqWithSideEffect variant
+ |> TaskSeq.takeWhileInclusiveAsync (fun x -> task { return condWithGuard x })
+ |> verifyDigitsAsString "ABCDEF"
+ }
[]
[]
[]
[]
[]
- let ``TaskSeq-takeWhileXXX prove it does not read beyond the failing yield`` (inclusive, isAsync) = task {
+ let ``TaskSeq-takeWhile and variants prove it does not read beyond the failing yield`` (inclusive, isAsync) = task {
let mutable x = 42 // for this test, the potential mutation should not actually occur
let functionToTest = getFunction inclusive isAsync ((=) 42)
@@ -165,7 +183,7 @@ module SideEffects =
[]
[]
[]
- let ``TaskSeq-takeWhileXXX prove side effects are executed`` (inclusive, isAsync) = task {
+ let ``TaskSeq-takeWhile and variants prove side effects are executed`` (inclusive, isAsync) = task {
let mutable x = 41
let functionToTest = getFunction inclusive isAsync ((>) 50)
@@ -180,6 +198,7 @@ module SideEffects =
let expectedFirst = if inclusive then [| 42; 44 * 2 |] else [| 42 |]
let expectedRepeat = if inclusive then [| 45; 47 * 2 |] else [| 45 |]
+ x |> should equal 41
let! first = items |> functionToTest |> TaskSeq.toArrayAsync
x |> should equal 44
let! repeat = items |> functionToTest |> TaskSeq.toArrayAsync
@@ -201,6 +220,7 @@ module SideEffects =
first |> should equal expected
// side effect, reiterating causes it to resume from where we left it (minus the failing item)
+ // which means the original sequence has now changed due to the side effect
let! repeat =
TaskSeq.takeWhile (fun x -> x < 5) ts
|> TaskSeq.toArrayAsync
@@ -220,6 +240,7 @@ module SideEffects =
first |> should equal expected
// side effect, reiterating causes it to resume from where we left it (minus the failing item)
+ // which means the original sequence has now changed due to the side effect
let! repeat =
TaskSeq.takeWhileInclusiveAsync (fun x -> task { return x < 5 }) ts
|> TaskSeq.toArrayAsync
@@ -233,7 +254,7 @@ module Other =
[]
[]
[]
- let ``TaskSeq-takeWhileXXX exclude all items after predicate fails`` (inclusive, isAsync) =
+ let ``TaskSeq-takeWhile and variants excludes all items after predicate fails`` (inclusive, isAsync) =
let functionToTest = With.getFunction inclusive isAsync
[ 1; 2; 2; 3; 3; 2; 1 ]
@@ -246,7 +267,7 @@ module Other =
[]
[]
[]
- let ``TaskSeq-takeWhileXXX stops consuming after predicate fails`` (inclusive, isAsync) =
+ let ``TaskSeq-takeWhile and variants stops consuming after predicate fails`` (inclusive, isAsync) =
let functionToTest = With.getFunction inclusive isAsync
seq {
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index be9961f7..d3aedcb9 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -297,6 +297,10 @@ type TaskSeq private () =
static member takeWhileAsync predicate source = Internal.takeWhile Exclusive (PredicateAsync predicate) source
static member takeWhileInclusive predicate source = Internal.takeWhile Inclusive (Predicate predicate) source
static member takeWhileInclusiveAsync predicate source = Internal.takeWhile Inclusive (PredicateAsync predicate) source
+ static member skipWhile predicate source = Internal.skipWhile Exclusive (Predicate predicate) source
+ static member skipWhileAsync predicate source = Internal.skipWhile Exclusive (PredicateAsync predicate) source
+ static member skipWhileInclusive predicate source = Internal.skipWhile Inclusive (Predicate predicate) source
+ static member skipWhileInclusiveAsync predicate source = Internal.skipWhile Inclusive (PredicateAsync predicate) source
static member tryPick chooser source = Internal.tryPick (TryPick chooser) source
static member tryPickAsync chooser source = Internal.tryPick (TryPickAsync chooser) source
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index 68b8d4e2..c34895d2 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -830,10 +830,10 @@ type TaskSeq =
static member takeWhile: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
///
- /// Returns a sequence that, when iterated, yields elements of the underlying sequence while the
+ /// Returns a task sequence that, when iterated, yields elements of the underlying sequence while the
/// given asynchronous function returns , and then returns no further elements.
/// The first element where the predicate returns is not included in the resulting sequence
- /// (see also ).
+ /// (see also ).
/// If is synchronous, consider using .
///
///
@@ -844,7 +844,7 @@ type TaskSeq =
static member takeWhileAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> TaskSeq<'T>
///
- /// Returns a sequence that, when iterated, yields elements of the underlying sequence until the given
+ /// Returns a task sequence that, when iterated, yields elements of the underlying sequence until the given
/// function returns , returns that element
/// and then returns no further elements (see also ). This function returns
/// at least one element of a non-empty sequence, or the empty task sequence if the input is empty.
@@ -858,9 +858,9 @@ type TaskSeq =
static member takeWhileInclusive: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
///
- /// Returns a sequence that, when iterated, yields elements of the underlying sequence until the given
+ /// Returns a task sequence that, when iterated, yields elements of the underlying sequence until the given
/// asynchronous function returns , returns that element
- /// and then returns no further elements (see also ). This function returns
+ /// and then returns no further elements (see also ). This function returns
/// at least one element of a non-empty sequence, or the empty task sequence if the input is empty.
/// If is synchronous, consider using .
///
@@ -871,6 +871,62 @@ type TaskSeq =
/// Thrown when the input task sequence is null.
static member takeWhileInclusiveAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> TaskSeq<'T>
+ ///
+ /// Returns a task sequence that, when iterated, skips elements of the underlying sequence while the
+ /// given function returns , and then yields the remaining
+ /// elements. The first element where the predicate returns is returned, which means that this
+ /// function will skip 0 or more elements (see also ).
+ /// If is asynchronous, consider using .
+ ///
+ ///
+ /// A function that evaluates to false when no more items should be skipped.
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member skipWhile: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
+
+ ///
+ /// Returns a task sequence that, when iterated, skips elements of the underlying sequence while the
+ /// given asynchronous function returns , and then yields the
+ /// remaining elements. The first element where the predicate returns is returned, which
+ /// means that this function will skip 0 or more elements (see also ).
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// An asynchronous function that evaluates to false when no more items should be skipped.
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member skipWhileAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> TaskSeq<'T>
+
+ ///
+ /// Returns a task sequence that, when iterated, skips elements of the underlying sequence until the given
+ /// function returns , also skips that element
+ /// and then yields the remaining elements (see also ). This function skips
+ /// at least one element of a non-empty sequence, or returns the empty task sequence if the input is empty.
+ /// If is asynchronous, consider using .
+ /// `
+ ///
+ /// A function that evaluates to false when no more items should be skipped.
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member skipWhileInclusive: predicate: ('T -> bool) -> source: TaskSeq<'T> -> TaskSeq<'T>
+
+ ///
+ /// Returns a task sequence that, when iterated, skips elements of the underlying sequence until the given
+ /// function returns , also skips that element
+ /// and then yields the remaining elements (see also ). This function skips
+ /// at least one element of a non-empty sequence, or returns the empty task sequence if the input is empty.
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// An asynchronous function that evaluates to false when no more items should be skipped.
+ /// The input task sequence.
+ /// The resulting task sequence.
+ /// Thrown when the input task sequence is null.
+ static member skipWhileInclusiveAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> TaskSeq<'T>
+
///
/// Applies the given function to successive elements, returning the first result where
/// the function returns .
diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
index d6a33421..e59d50c3 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
@@ -13,9 +13,9 @@ type internal AsyncEnumStatus =
[]
type internal WhileKind =
- /// The item under test is included even if false
+ /// The item under test is included (or skipped) even when the predicate returns false
| Inclusive
- /// The item under test is always excluded
+ /// The item under test is always excluded (or not skipped)
| Exclusive
[]
@@ -648,9 +648,9 @@ module internal TaskSeqInternal =
use e = source.GetAsyncEnumerator CancellationToken.None
for _ in 1..count do
- let! ok = e.MoveNextAsync()
+ let! hasMore = e.MoveNextAsync()
- if not ok then
+ if not hasMore then
raiseInsufficient ()
while! e.MoveNextAsync() do
@@ -731,53 +731,135 @@ module internal TaskSeqInternal =
taskSeq {
use e = source.GetAsyncEnumerator CancellationToken.None
- let! step = e.MoveNextAsync()
- let mutable more = step
+ let! notEmpty = e.MoveNextAsync()
+ let mutable more = notEmpty
match whileKind, predicate with
- | Exclusive, Predicate predicate ->
+ | Exclusive, Predicate predicate -> // takeWhile
while more do
let value = e.Current
more <- predicate value
if more then
+ // yield ONLY if predicate is true
yield value
- let! ok = e.MoveNextAsync()
- more <- ok
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
- | Inclusive, Predicate predicate ->
+ | Inclusive, Predicate predicate -> // takeWhileInclusive
while more do
let value = e.Current
more <- predicate value
+ // yield regardless of result of predicate
yield value
if more then
- let! ok = e.MoveNextAsync()
- more <- ok
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
- | Exclusive, PredicateAsync predicate ->
+ | Exclusive, PredicateAsync predicate -> // takeWhileAsync
while more do
let value = e.Current
let! passed = predicate value
more <- passed
if more then
+ // yield ONLY if predicate is true
yield value
- let! ok = e.MoveNextAsync()
- more <- ok
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
- | Inclusive, PredicateAsync predicate ->
+ | Inclusive, PredicateAsync predicate -> // takeWhileInclusiveAsync
while more do
let value = e.Current
let! passed = predicate value
more <- passed
+ // yield regardless of predicate
yield value
if more then
- let! ok = e.MoveNextAsync()
- more <- ok
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
+ }
+
+ let skipWhile whileKind predicate (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ taskSeq {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let! moveFirst = e.MoveNextAsync()
+ let mutable more = moveFirst
+
+ match whileKind, predicate with
+ | Exclusive, Predicate predicate -> // skipWhile
+ while more && predicate e.Current do
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
+
+ if more then
+ // yield the last one where the predicate was false
+ // (this ensures we skip 0 or more)
+ yield e.Current
+
+ while! e.MoveNextAsync() do // get the rest
+ yield e.Current
+
+ | Inclusive, Predicate predicate -> // skipWhileInclusive
+ while more && predicate e.Current do
+ let! hasMore = e.MoveNextAsync()
+ more <- hasMore
+
+ if more then
+ // yield the rest (this ensures we skip 1 or more)
+ while! e.MoveNextAsync() do
+ yield e.Current
+
+ | Exclusive, PredicateAsync predicate -> // skipWhileAsync
+ let mutable cont = true
+
+ if more then
+ let! hasMore = predicate e.Current
+ cont <- hasMore
+
+ while more && cont do
+ let! moveNext = e.MoveNextAsync()
+
+ if moveNext then
+ let! hasMore = predicate e.Current
+ cont <- hasMore
+
+ more <- moveNext
+
+ if more then
+ // yield the last one where the predicate was false
+ // (this ensures we skip 0 or more)
+ yield e.Current
+
+ while! e.MoveNextAsync() do // get the rest
+ yield e.Current
+
+ | Inclusive, PredicateAsync predicate -> // skipWhileInclusiveAsync
+ let mutable cont = true
+
+ if more then
+ let! hasMore = predicate e.Current
+ cont <- hasMore
+
+ while more && cont do
+ let! moveNext = e.MoveNextAsync()
+
+ if moveNext then
+ let! hasMore = predicate e.Current
+ cont <- hasMore
+
+ more <- moveNext
+
+ if more then
+ // get the rest, this gives 1 or more semantics
+ while! e.MoveNextAsync() do
+ yield e.Current
}
// Consider turning using an F# version of this instead?