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 ff7ae5e..d625d93 100644
--- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
+++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj
@@ -24,6 +24,7 @@
+
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs
index 04a652f..ee65f1e 100644
--- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs
@@ -82,7 +82,7 @@ module Immutable =
module SideEffects =
[)>]
- let ``TaskSeq-exists KeyNotFoundException only sometimes for mutated state`` variant = task {
+ let ``TaskSeq-exists success only sometimes for mutated state`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let finder = (=) 11
@@ -100,7 +100,7 @@ module SideEffects =
}
[)>]
- let ``TaskSeq-existsAsync KeyNotFoundException only sometimes for mutated state`` variant = task {
+ let ``TaskSeq-existsAsync success only sometimes for mutated state`` variant = task {
let ts = Gen.getSeqWithSideEffect variant
let finder x = task { return x = 11 }
@@ -201,7 +201,7 @@ module SideEffects =
found |> should be True
i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated
- // find some next item. We do get a new iterator, but mutable state is now starting at '1'
+ // find some next item. We do get a new iterator, but mutable state is now still starting at '0'
let! found = ts |> TaskSeq.exists ((=) 4)
found |> should be True
i |> should equal 4 // only partial evaluation!
@@ -221,7 +221,7 @@ module SideEffects =
found |> should be True
i |> should equal 0 // notice that it should be one higher if the statement after 'yield' is evaluated
- // find some next item. We do get a new iterator, but mutable state is now starting at '1'
+ // find some next item. We do get a new iterator, but mutable state is now still starting at '0'
let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 4 })
found |> should be True
i |> should equal 4 // only partial evaluation!
diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs
new file mode 100644
index 0000000..8c47dea
--- /dev/null
+++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Forall.Tests.fs
@@ -0,0 +1,200 @@
+module TaskSeq.Tests.Forall
+
+open Xunit
+open FsUnit.Xunit
+
+open FSharp.Control
+
+//
+// TaskSeq.forall
+// TaskSeq.forallAsyncc
+//
+
+module EmptySeq =
+ []
+ let ``Null source is invalid`` () =
+ assertNullArg
+ <| fun () -> TaskSeq.forall (fun _ -> false) null
+
+ assertNullArg
+ <| fun () -> TaskSeq.forallAsync (fun _ -> Task.fromResult false) null
+
+ [)>]
+ let ``TaskSeq-forall always returns true`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.forall ((=) 12)
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-forallAsync always returns true`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.forallAsync (fun x -> task { return x = 12 })
+ |> Task.map (should be True)
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-forall sad path returns false`` variant = task {
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forall ((=) 0)
+ |> Task.map (should be False)
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forall ((>) 9) // lt
+ |> Task.map (should be False)
+ }
+
+ [)>]
+ let ``TaskSeq-forallAsync sad path returns false`` variant = task {
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forallAsync (fun x -> task { return x = 0 })
+ |> Task.map (should be False)
+
+ do!
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forallAsync (fun x -> task { return x < 9 })
+ |> Task.map (should be False)
+ }
+
+ [)>]
+ let ``TaskSeq-forall happy path whole seq true`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forall (fun x -> x < 6 || x > 5)
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-forallAsync happy path whole seq true`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.forallAsync (fun x -> task { return x <= 10 && x >= 0 })
+ |> Task.map (should be True)
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-forall mutated state can change result`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+ let predicate x = x > 10
+
+ // first: false
+ let! found = TaskSeq.forall predicate ts
+ found |> should be False // fails on first item, not many side effects yet
+
+ // ensure side effects executes
+ do! consumeTaskSeq ts
+
+ // find again: found now, because of side effects
+ let! found = TaskSeq.forall predicate ts
+ found |> should be True
+
+ // find once more, still true, as numbers increase
+ do! consumeTaskSeq ts // ensure side effects executes
+ let! found = TaskSeq.forall predicate ts
+ found |> should be True
+ }
+
+ [)>]
+ let ``TaskSeq-forallAsync mutated state can change result`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+ let predicate x = Task.fromResult (x > 10)
+
+ // first: false
+ let! found = TaskSeq.forallAsync predicate ts
+ found |> should be False // fails on first item, not many side effects yet
+
+ // ensure side effects executes
+ do! consumeTaskSeq ts
+
+ // find again: found now, because of side effects
+ let! found = TaskSeq.forallAsync predicate ts
+ found |> should be True
+
+ // find once more, still true, as numbers increase
+ do! consumeTaskSeq ts // ensure side effects executes
+ let! found = TaskSeq.forallAsync predicate ts
+ found |> should be True
+ }
+
+ []
+ let ``TaskSeq-forall _specialcase_ prove we don't read past the first failing item`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ i <- i + 1
+ yield i
+ }
+
+ let! found = ts |> TaskSeq.forall ((>) 3)
+ found |> should be False
+ i |> should equal 3 // only partial evaluation!
+
+ // find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'.
+ let! found = ts |> TaskSeq.forall ((<=) 4)
+ found |> should be True
+ i |> should equal 13 // we evaluated to the end
+ }
+
+ []
+ let ``TaskSeq-forallAsync _specialcase_ prove we don't read past the first failing item`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ i <- i + 1
+ yield i
+ }
+
+ let! found = ts |> TaskSeq.forallAsync (fun x -> Task.fromResult (x < 3))
+ found |> should be False
+ i |> should equal 3 // only partial evaluation!
+
+ // find next item. We do get a new iterator, but mutable state is now starting at '3', so first item now returned is '4'.
+ let! found =
+ ts
+ |> TaskSeq.forallAsync (fun x -> Task.fromResult (x >= 4))
+
+ found |> should be True
+ i |> should equal 13 // we evaluated to the end
+ }
+
+
+ []
+ let ``TaskSeq-forall _specialcase_ prove statement after first false result is not evaluated`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ yield i
+ i <- i + 1
+ }
+
+ let! found = ts |> TaskSeq.forall ((>) 0)
+ found |> should be False
+ i |> should equal 0 // notice that it should be one higher if the statement after 'yield' was evaluated
+
+ // find some next item. We do get a new iterator, but mutable state is still starting at '0'
+ let! found = ts |> TaskSeq.forall ((>) 4)
+ found |> should be False
+ i |> should equal 4 // only partial evaluation!
+ }
+
+ []
+ let ``TaskSeq-forallAsync _specialcase_ prove statement after first false result is not evaluated`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ yield i
+ i <- i + 1
+ }
+
+ let! found = ts |> TaskSeq.forallAsync (fun x -> Task.fromResult (x < 0))
+ found |> should be False
+ i |> should equal 0 // notice that it should be one higher if the statement after 'yield' was evaluated
+
+ // find some next item. We do get a new iterator, but mutable state is still starting at '0'
+ let! found = ts |> TaskSeq.forallAsync (fun x -> Task.fromResult (x < 4))
+ found |> should be False
+ i |> should equal 4 // only partial evaluation!
+ }
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs
index 7775e2e..710dadd 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs
@@ -358,6 +358,9 @@ type TaskSeq private () =
static member except itemsToExclude source = Internal.except itemsToExclude source
static member exceptOfSeq itemsToExclude source = Internal.exceptOfSeq itemsToExclude source
+ static member forall predicate source = Internal.forall (Predicate predicate) source
+ static member forallAsync predicate source = Internal.forall (PredicateAsync predicate) source
+
static member exists predicate source =
Internal.tryFind (Predicate predicate) source
|> Task.map Option.isSome
diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
index 3c4fd63..cb5eefe 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi
+++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi
@@ -875,6 +875,30 @@ type TaskSeq =
/// Thrown when the input task sequence is null.
static member whereAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> TaskSeq<'T>
+ ///
+ /// Tests if all elements of the sequence satisfy the given predicate. Stops evaluating
+ /// as soon as returns .
+ /// If is asynchronous, consider using .
+ ///
+ ///
+ /// A function to test an element of the input sequence.
+ /// The input task sequence.
+ /// A task that, after awaiting, holds true if every element of the sequence satisfies the predicate; false otherwise.
+ /// Thrown when the input task sequence is null.
+ static member forall: predicate: ('T -> bool) -> source: TaskSeq<'T> -> Task
+
+ ///
+ /// Tests if all elements of the sequence satisfy the given asynchronous predicate. Stops evaluating
+ /// as soon as returns .
+ /// If is synchronous, consider using .
+ ///
+ ///
+ /// A function to test an element of the input sequence.
+ /// The input task sequence.
+ /// A task that, after awaiting, holds true if every element of the sequence satisfies the predicate; false otherwise.
+ /// Thrown when the input task sequence is null.
+ static member forallAsync: predicate: ('T -> #Task) -> source: TaskSeq<'T> -> Task
+
///
/// Returns a task sequence that, when iterated, skips elements of the underlying
/// sequence, and then yields the remainder. Raises an exception if there are not
diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
index 5827637..d7773ce 100644
--- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
@@ -690,18 +690,54 @@ module internal TaskSeqInternal =
taskSeq {
match predicate with
- | Predicate predicate ->
+ | Predicate syncPredicate ->
for item in source do
- if predicate item then
+ if syncPredicate item then
yield item
- | PredicateAsync predicate ->
+ | PredicateAsync asyncPredicate ->
for item in source do
- match! predicate item with
+ match! asyncPredicate item with
| true -> yield item
| false -> ()
}
+ let forall predicate (source: TaskSeq<_>) =
+ checkNonNull (nameof source) source
+
+ match predicate with
+ | Predicate syncPredicate -> task {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let mutable state = true
+ let! cont = e.MoveNextAsync()
+ let mutable hasMore = cont
+
+ while state && hasMore do
+ state <- syncPredicate e.Current
+
+ if state then
+ let! cont = e.MoveNextAsync()
+ hasMore <- cont
+
+ return state
+ }
+
+ | PredicateAsync asyncPredicate -> task {
+ use e = source.GetAsyncEnumerator CancellationToken.None
+ let mutable state = true
+ let! cont = e.MoveNextAsync()
+ let mutable hasMore = cont
+
+ while state && hasMore do
+ let! pred = asyncPredicate e.Current
+ state <- pred
+
+ if state then
+ let! cont = e.MoveNextAsync()
+ hasMore <- cont
+
+ return state
+ }
let skipOrTake skipOrTake count (source: TaskSeq<_>) =
checkNonNull (nameof source) source