diff --git a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
index 19815165..e7bfdf2b 100644
--- a/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
+++ b/src/FSharpy.TaskSeq.Test/FSharpy.TaskSeq.Test.fsproj
@@ -16,8 +16,10 @@
+
+
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Contains.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Contains.Tests.fs
new file mode 100644
index 00000000..a4442fb8
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Contains.Tests.fs
@@ -0,0 +1,116 @@
+module FSharpy.Tests.Contains
+
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+
+//
+// TaskSeq.contains
+//
+
+module EmptySeq =
+ [)>]
+ let ``TaskSeq-contains returns false`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.contains 12
+ |> Task.map (should be False)
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-contains sad path returns false`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.contains 0
+ |> Task.map (should be False)
+
+ [)>]
+ let ``TaskSeq-contains happy path middle of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.contains 5
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-contains happy path first item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.contains 1
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-contains happy path last item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.contains 10
+ |> Task.map (should be True)
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-contains KeyNotFoundException only sometimes for mutated state`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+
+ // first: false
+ let! found = TaskSeq.contains 11 ts
+ found |> should be False
+
+ // find again: found now, because of side effects
+ let! found = TaskSeq.contains 11 ts
+ found |> should be True
+
+ // find once more: false
+ let! found = TaskSeq.contains 11 ts
+ found |> should be False
+ }
+
+ []
+ let ``TaskSeq-contains _specialcase_ prove we don't read past the found item`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ i <- i + 1
+ yield i
+ }
+
+ let! found = ts |> TaskSeq.contains 3
+ found |> should be True
+ 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.contains 4
+ found |> should be True
+ i |> should equal 4 // only partial evaluation!
+ }
+
+ []
+ let ``TaskSeq-contains _specialcase_ prove we don't read past the found item v2`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ yield 42
+ i <- i + 1
+ i <- i + 1
+ }
+
+ let! found = ts |> TaskSeq.contains 42
+ found |> should be True
+ i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
+ }
+
+ []
+ let ``TaskSeq-contains _specialcase_ prove statement after yield 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.contains 0
+ 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'
+ let! found = ts |> TaskSeq.contains 4
+ found |> should be True
+ i |> should equal 4 // only partial evaluation!
+ }
diff --git a/src/FSharpy.TaskSeq.Test/TaskSeq.Exists.Tests.fs b/src/FSharpy.TaskSeq.Test/TaskSeq.Exists.Tests.fs
new file mode 100644
index 00000000..a717b18f
--- /dev/null
+++ b/src/FSharpy.TaskSeq.Test/TaskSeq.Exists.Tests.fs
@@ -0,0 +1,221 @@
+module FSharpy.Tests.Exists
+
+open Xunit
+open FsUnit.Xunit
+open FsToolkit.ErrorHandling
+
+open FSharpy
+
+//
+// TaskSeq.exists
+// TaskSeq.existsAsyncc
+//
+
+module EmptySeq =
+ [)>]
+ let ``TaskSeq-exists returns false`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.exists ((=) 12)
+ |> Task.map (should be False)
+
+ [)>]
+ let ``TaskSeq-existsAsync returns false`` variant =
+ Gen.getEmptyVariant variant
+ |> TaskSeq.existsAsync (fun x -> task { return x = 12 })
+ |> Task.map (should be False)
+
+module Immutable =
+ [)>]
+ let ``TaskSeq-exists sad path returns false`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.exists ((=) 0)
+ |> Task.map (should be False)
+
+ [)>]
+ let ``TaskSeq-existsAsync sad path return false`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.existsAsync (fun x -> task { return x = 0 })
+ |> Task.map (should be False)
+
+ [)>]
+ let ``TaskSeq-exists happy path middle of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.exists (fun x -> x < 6 && x > 4)
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-existsAsync happy path middle of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.existsAsync (fun x -> task { return x < 6 && x > 4 })
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-exists happy path first item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.exists ((=) 1)
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-existsAsync happy path first item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.existsAsync (fun x -> task { return x = 1 })
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-exists happy path last item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.exists ((=) 10)
+ |> Task.map (should be True)
+
+ [)>]
+ let ``TaskSeq-existsAsync happy path last item of seq`` variant =
+ Gen.getSeqImmutable variant
+ |> TaskSeq.existsAsync (fun x -> task { return x = 10 })
+ |> Task.map (should be True)
+
+module SideEffects =
+ [)>]
+ let ``TaskSeq-exists KeyNotFoundException only sometimes for mutated state`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+ let finder = (=) 11
+
+ // first: false
+ let! found = TaskSeq.exists finder ts
+ found |> should be False
+
+ // find again: found now, because of side effects
+ let! found = TaskSeq.exists finder ts
+ found |> should be True
+
+ // find once more: false
+ let! found = TaskSeq.exists finder ts
+ found |> should be False
+ }
+
+ [)>]
+ let ``TaskSeq-existsAsync KeyNotFoundException only sometimes for mutated state`` variant = task {
+ let ts = Gen.getSeqWithSideEffect variant
+ let finder x = task { return x = 11 }
+
+ // first: false
+ let! found = TaskSeq.existsAsync finder ts
+ found |> should be False
+
+ // find again: found now, because of side effects
+ let! found = TaskSeq.existsAsync finder ts
+ found |> should be True
+
+ // find once more: false
+ let! found = TaskSeq.existsAsync finder ts
+ found |> should be False
+ }
+
+ []
+ let ``TaskSeq-exists _specialcase_ prove we don't read past the found item`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ i <- i + 1
+ yield i
+ }
+
+ let! found = ts |> TaskSeq.exists ((=) 3)
+ found |> should be True
+ 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.exists ((=) 4)
+ found |> should be True
+ i |> should equal 4 // only partial evaluation!
+ }
+
+ []
+ let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ for _ in 0..9 do
+ i <- i + 1
+ yield i
+ }
+
+ let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 3 })
+ found |> should be True
+ 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.existsAsync (fun x -> task { return x = 4 })
+ found |> should be True
+ i |> should equal 4
+ }
+
+ []
+ let ``TaskSeq-exists _specialcase_ prove we don't read past the found item v2`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ yield 42
+ i <- i + 1
+ i <- i + 1
+ }
+
+ let! found = ts |> TaskSeq.exists ((=) 42)
+ found |> should be True
+ i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
+ }
+
+ []
+ let ``TaskSeq-existsAsync _specialcase_ prove we don't read past the found item v2`` () = task {
+ let mutable i = 0
+
+ let ts = taskSeq {
+ yield 42
+ i <- i + 1
+ i <- i + 1
+ }
+
+ let! found = ts |> TaskSeq.existsAsync (fun x -> task { return x = 42 })
+ found |> should be True
+ i |> should equal 0 // because no MoveNext after found item, the last statements are not executed
+ }
+
+ []
+ let ``TaskSeq-exists _specialcase_ prove statement after yield 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.exists ((=) 0)
+ 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'
+ let! found = ts |> TaskSeq.exists ((=) 4)
+ found |> should be True
+ i |> should equal 4 // only partial evaluation!
+ }
+
+ []
+ let ``TaskSeq-existsAsync _specialcase_ prove statement after yield 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.existsAsync (fun x -> task { return x = 0 })
+ 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'
+ 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/FSharpy.TaskSeq/TaskSeq.fs b/src/FSharpy.TaskSeq/TaskSeq.fs
index 64cd33c9..1bc469bc 100644
--- a/src/FSharpy.TaskSeq/TaskSeq.fs
+++ b/src/FSharpy.TaskSeq/TaskSeq.fs
@@ -254,6 +254,18 @@ module TaskSeq =
let tryFindIndex predicate source = Internal.tryFindIndex (Predicate predicate) source
let tryFindIndexAsync predicate source = Internal.tryFindIndex (PredicateAsync predicate) source
+ let exists predicate source =
+ Internal.tryFind (Predicate predicate) source
+ |> Task.map (Option.isSome)
+
+ let existsAsync predicate source =
+ Internal.tryFind (PredicateAsync predicate) source
+ |> Task.map (Option.isSome)
+
+ let contains value source =
+ Internal.tryFind (Predicate((=) value)) source
+ |> Task.map (Option.isSome)
+
let pick chooser source = task {
match! Internal.tryPick (TryPick chooser) source with
| Some item -> return item
diff --git a/src/FSharpy.TaskSeq/TaskSeq.fsi b/src/FSharpy.TaskSeq/TaskSeq.fsi
index 493111fa..4ca35ba1 100644
--- a/src/FSharpy.TaskSeq/TaskSeq.fsi
+++ b/src/FSharpy.TaskSeq/TaskSeq.fsi
@@ -405,10 +405,51 @@ module TaskSeq =
/// asynchronous function returns .
/// If does not need to be asynchronous, consider using .
///
+ ///
/// Thrown if no element returns when
/// evaluated by the function.
val findIndexAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
+ ///
+ /// Tests if the sequence contains the specified element. Returns
+ /// if contains the specified element;
+ /// otherwise.
+ ///
+ ///
+ /// The value to locate in the input sequence.
+ /// The input sequence.
+ /// True if the input sequence contains the specified element; false otherwise.
+ /// Thrown when the input sequence is null.
+ val contains<'T when 'T: equality> : value: 'T -> source: taskSeq<'T> -> Task
+
+ ///
+ /// Tests if any element of the task sequence in satisfies
+ /// the given .
+ /// The function is applied to the elements of the input sequence. If any application
+ /// returns then the overall result is and no further elements are evaluated and tested.
+ /// Otherwise, is returned.
+ ///
+ ///
+ /// A function to test each item of the input sequence.
+ /// The input sequence. ///
+ /// True if any result from the predicate is true; false otherwise. ///
+ /// Thrown when the input sequence is null.
+ val exists: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task
+
+ ///
+ /// Tests if any element of the task sequence in satisfies
+ /// the given async .
+ /// The function is applied to the elements of the input sequence. If any application
+ /// returns then the overall result is and no further elements are evaluated and tested.
+ /// Otherwise, is returned.
+ ///
+ ///
+ /// A function to test each item of the input sequence.
+ /// The input sequence. ///
+ /// True if any result from the predicate is true; false otherwise. ///
+ /// Thrown when the input sequence is null.
+ val existsAsync: predicate: ('T -> #Task) -> source: taskSeq<'T> -> Task
+
///
/// Zips two task sequences, returning a taskSeq of the tuples of each sequence, in order. May raise ArgumentException
/// if the sequences are or unequal length.
diff --git a/src/FSharpy.TaskSeq/TaskSeqInternal.fs b/src/FSharpy.TaskSeq/TaskSeqInternal.fs
index 7d3870ef..256ad7ad 100644
--- a/src/FSharpy.TaskSeq/TaskSeqInternal.fs
+++ b/src/FSharpy.TaskSeq/TaskSeqInternal.fs
@@ -29,7 +29,7 @@ type ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U option>> =
| TryPickAsync of async_try_pick: ('T -> 'TaskOption)
[]
-type PredicateAction<'T, 'U, 'TaskBool when 'TaskBool :> Task> =
+type PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> =
| Predicate of try_filter: ('T -> bool)
| PredicateAsync of async_try_filter: ('T -> 'TaskBool)