Skip to content

Commit 98b51e6

Browse files
[Repo Assist] Add AsyncSeq.findAsync, existsAsync, forallAsync (#257)
* Add AsyncSeq.existsAsync, forallAsync, findAsync Three new async-predicate combinators completing the symmetry with their sync-predicate counterparts (exists, forall, find): - existsAsync: async predicate variant of AsyncSeq.exists - forallAsync: async predicate variant of AsyncSeq.forall - findAsync: async predicate variant of AsyncSeq.find (raises KeyNotFoundException) 8 new tests added; 256 total pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger CI checks --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6b7e2dc commit 98b51e6

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

RELEASE_NOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### 4.4.0
2+
3+
* Added `AsyncSeq.findAsync` — async-predicate variant of `AsyncSeq.find`; raises `KeyNotFoundException` if no match, mirroring `Seq.find`.
4+
* Added `AsyncSeq.existsAsync` — async-predicate variant of `AsyncSeq.exists`, mirroring `Seq.exists`.
5+
* Added `AsyncSeq.forallAsync` — async-predicate variant of `AsyncSeq.forall`, mirroring `Seq.forall`.
6+
17
### 4.3.0
28

39
* Added `AsyncSeq.head` — returns the first element of the sequence; raises `InvalidOperationException` if empty, mirroring `Seq.head`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,12 +1180,21 @@ module AsyncSeq =
11801180
let find f (source : AsyncSeq<'T>) =
11811181
source |> pick (fun v -> if f v then Some v else None)
11821182

1183+
let findAsync f (source : AsyncSeq<'T>) =
1184+
source |> pickAsync (fun v -> async { let! b = f v in return if b then Some v else None })
1185+
11831186
let exists f (source : AsyncSeq<'T>) =
11841187
source |> tryFind f |> Async.map Option.isSome
11851188

1189+
let existsAsync f (source : AsyncSeq<'T>) =
1190+
source |> tryFindAsync f |> Async.map Option.isSome
1191+
11861192
let forall f (source : AsyncSeq<'T>) =
11871193
source |> exists (f >> not) |> Async.map not
11881194

1195+
let forallAsync f (source : AsyncSeq<'T>) =
1196+
source |> existsAsync (fun v -> async { let! b = f v in return not b }) |> Async.map not
1197+
11891198
let foldAsync f (state:'State) (source : AsyncSeq<'T>) =
11901199
match source with
11911200
| :? AsyncSeqOp<'T> as source -> source.FoldAsync f state

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,22 @@ module AsyncSeq =
327327
/// Raises KeyNotFoundException if no matching element is found.
328328
val find : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<'T>
329329

330+
/// Asynchronously find the first value in a sequence for which the async predicate returns true.
331+
/// Raises KeyNotFoundException if no matching element is found.
332+
val findAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<'T>
333+
330334
/// Asynchronously determine if there is a value in the sequence for which the predicate returns true
331335
val exists : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<bool>
332336

337+
/// Asynchronously determine if there is a value in the sequence for which the async predicate returns true
338+
val existsAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<bool>
339+
333340
/// Asynchronously determine if the predicate returns true for all values in the sequence
334341
val forall : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<bool>
335342

343+
/// Asynchronously determine if the async predicate returns true for all values in the sequence
344+
val forallAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<bool>
345+
336346
/// Return an asynchronous sequence which, when iterated, includes an integer indicating the index of each element in the sequence.
337347
val indexed : source:AsyncSeq<'T> -> AsyncSeq<int64 * 'T>
338348

tests/FSharp.Control.AsyncSeq.Tests/AsyncSeqTests.fs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3141,3 +3141,71 @@ let ``AsyncSeq.tail on empty returns empty`` () =
31413141
|> AsyncSeq.toListAsync
31423142
|> Async.RunSynchronously
31433143
Assert.AreEqual([], result)
3144+
3145+
// ===== findAsync / existsAsync / forallAsync =====
3146+
3147+
[<Test>]
3148+
let ``AsyncSeq.findAsync returns matching element`` () =
3149+
for i in 0 .. 10 do
3150+
let ls = [ 1 .. i + 1 ]
3151+
let result =
3152+
AsyncSeq.ofSeq ls
3153+
|> AsyncSeq.findAsync (fun x -> async { return x = i + 1 })
3154+
|> Async.RunSynchronously
3155+
Assert.AreEqual(i + 1, result)
3156+
3157+
[<Test>]
3158+
let ``AsyncSeq.findAsync raises KeyNotFoundException when no match`` () =
3159+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3160+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3161+
|> AsyncSeq.findAsync (fun x -> async { return x = 99 })
3162+
|> Async.RunSynchronously |> ignore)
3163+
|> ignore
3164+
3165+
[<Test>]
3166+
let ``AsyncSeq.existsAsync returns true when element found`` () =
3167+
let result =
3168+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3169+
|> AsyncSeq.existsAsync (fun x -> async { return x = 2 })
3170+
|> Async.RunSynchronously
3171+
Assert.IsTrue(result)
3172+
3173+
[<Test>]
3174+
let ``AsyncSeq.existsAsync returns false on empty sequence`` () =
3175+
let result =
3176+
AsyncSeq.empty<int>
3177+
|> AsyncSeq.existsAsync (fun _ -> async { return true })
3178+
|> Async.RunSynchronously
3179+
Assert.IsFalse(result)
3180+
3181+
[<Test>]
3182+
let ``AsyncSeq.existsAsync returns false when no match`` () =
3183+
let result =
3184+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3185+
|> AsyncSeq.existsAsync (fun x -> async { return x = 99 })
3186+
|> Async.RunSynchronously
3187+
Assert.IsFalse(result)
3188+
3189+
[<Test>]
3190+
let ``AsyncSeq.forallAsync returns true when all match`` () =
3191+
let result =
3192+
AsyncSeq.ofSeq [ 2; 4; 6 ]
3193+
|> AsyncSeq.forallAsync (fun x -> async { return x % 2 = 0 })
3194+
|> Async.RunSynchronously
3195+
Assert.IsTrue(result)
3196+
3197+
[<Test>]
3198+
let ``AsyncSeq.forallAsync returns false when some do not match`` () =
3199+
let result =
3200+
AsyncSeq.ofSeq [ 2; 3; 6 ]
3201+
|> AsyncSeq.forallAsync (fun x -> async { return x % 2 = 0 })
3202+
|> Async.RunSynchronously
3203+
Assert.IsFalse(result)
3204+
3205+
[<Test>]
3206+
let ``AsyncSeq.forallAsync returns true on empty sequence`` () =
3207+
let result =
3208+
AsyncSeq.empty<int>
3209+
|> AsyncSeq.forallAsync (fun _ -> async { return false })
3210+
|> Async.RunSynchronously
3211+
Assert.IsTrue(result)

0 commit comments

Comments
 (0)