Skip to content

Commit 9e993d6

Browse files
authored
Merge pull request #297 from fsprojects/repo-assist/findback-2026-04-03-8769885a31ab339d
[Repo Assist] Add AsyncSeq.tryFindBack, findBack, tryFindBackAsync, findBackAsync
2 parents 148cff8 + 804baaa commit 9e993d6

File tree

5 files changed

+136
-1
lines changed

5 files changed

+136
-1
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
### 4.12.0
22

3+
* Added `AsyncSeq.tryFindBack` — returns the last element for which the predicate returns true, or `None` if no match. Mirrors `Array.tryFindBack` / `List.tryFindBack`.
4+
* Added `AsyncSeq.tryFindBackAsync` — async-predicate variant of `tryFindBack`.
5+
* Added `AsyncSeq.findBack` — returns the last element for which the predicate returns true; raises `KeyNotFoundException` if no match. Mirrors `Array.findBack` / `List.findBack`.
6+
* Added `AsyncSeq.findBackAsync` — async-predicate variant of `findBack`.
37
* Added `AsyncSeq.sortAsync` — asynchronous variant of `sort` returning `Async<'T[]>`, avoiding `Async.RunSynchronously` in async workflows.
48
* Added `AsyncSeq.sortByAsync` — asynchronous variant of `sortBy` returning `Async<'T[]>`.
59
* Added `AsyncSeq.sortDescendingAsync` — asynchronous variant of `sortDescending` returning `Async<'T[]>`.

src/FSharp.Control.AsyncSeq/AsyncSeq.fs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,41 @@ module AsyncSeq =
13331333
let findAsync f (source : AsyncSeq<'T>) =
13341334
source |> pickAsync (fun v -> async { let! b = f v in return if b then Some v else None })
13351335

1336+
let tryFindBack f (source : AsyncSeq<'T>) = async {
1337+
use ie = source.GetEnumerator()
1338+
let! v = ie.MoveNext()
1339+
let mutable b = v
1340+
let mutable res = None
1341+
while b.IsSome do
1342+
if f b.Value then res <- b
1343+
let! next = ie.MoveNext()
1344+
b <- next
1345+
return res }
1346+
1347+
let tryFindBackAsync f (source : AsyncSeq<'T>) = async {
1348+
use ie = source.GetEnumerator()
1349+
let! v = ie.MoveNext()
1350+
let mutable b = v
1351+
let mutable res = None
1352+
while b.IsSome do
1353+
let! matches = f b.Value
1354+
if matches then res <- b
1355+
let! next = ie.MoveNext()
1356+
b <- next
1357+
return res }
1358+
1359+
let findBack f (source : AsyncSeq<'T>) = async {
1360+
let! result = tryFindBack f source
1361+
match result with
1362+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1363+
| Some v -> return v }
1364+
1365+
let findBackAsync f (source : AsyncSeq<'T>) = async {
1366+
let! result = tryFindBackAsync f source
1367+
match result with
1368+
| None -> return raise (System.Collections.Generic.KeyNotFoundException("An element satisfying the predicate was not found in the collection."))
1369+
| Some v -> return v }
1370+
13361371
let tryFindIndex f (source : AsyncSeq<'T>) = async {
13371372
use ie = source.GetEnumerator()
13381373
let! first = ie.MoveNext()

src/FSharp.Control.AsyncSeq/AsyncSeq.fsi

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,22 @@ module AsyncSeq =
377377
/// Raises KeyNotFoundException if no matching element is found.
378378
val findAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<'T>
379379

380+
/// Asynchronously find the last value in a sequence for which the predicate returns true.
381+
/// Returns None if no matching element is found.
382+
val tryFindBack : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<'T option>
383+
384+
/// Asynchronously find the last value in a sequence for which the async predicate returns true.
385+
/// Returns None if no matching element is found.
386+
val tryFindBackAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<'T option>
387+
388+
/// Asynchronously find the last value in a sequence for which the predicate returns true.
389+
/// Raises KeyNotFoundException if no matching element is found.
390+
val findBack : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<'T>
391+
392+
/// Asynchronously find the last value in a sequence for which the async predicate returns true.
393+
/// Raises KeyNotFoundException if no matching element is found.
394+
val findBackAsync : predicate:('T -> Async<bool>) -> source:AsyncSeq<'T> -> Async<'T>
395+
380396
/// Asynchronously find the index of the first value in a sequence for which the predicate returns true.
381397
/// Returns None if no matching element is found.
382398
val tryFindIndex : predicate:('T -> bool) -> source:AsyncSeq<'T> -> Async<int option>

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3437,6 +3437,86 @@ let ``AsyncSeq.tryFindIndexAsync returns None when not found`` () =
34373437
|> Async.RunSynchronously
34383438
Assert.AreEqual(None, result)
34393439

3440+
// ===== tryFindBack / findBack / tryFindBackAsync / findBackAsync =====
3441+
3442+
[<Test>]
3443+
let ``AsyncSeq.tryFindBack returns last matching element`` () =
3444+
let result =
3445+
AsyncSeq.ofSeq [ 1; 2; 3; 4; 5 ]
3446+
|> AsyncSeq.tryFindBack (fun x -> x % 2 = 0)
3447+
|> Async.RunSynchronously
3448+
Assert.AreEqual(Some 4, result)
3449+
3450+
[<Test>]
3451+
let ``AsyncSeq.tryFindBack returns None when no match`` () =
3452+
let result =
3453+
AsyncSeq.ofSeq [ 1; 3; 5 ]
3454+
|> AsyncSeq.tryFindBack (fun x -> x % 2 = 0)
3455+
|> Async.RunSynchronously
3456+
Assert.AreEqual(None, result)
3457+
3458+
[<Test>]
3459+
let ``AsyncSeq.tryFindBack returns None on empty sequence`` () =
3460+
let result =
3461+
AsyncSeq.empty<int>
3462+
|> AsyncSeq.tryFindBack (fun _ -> true)
3463+
|> Async.RunSynchronously
3464+
Assert.AreEqual(None, result)
3465+
3466+
[<Test>]
3467+
let ``AsyncSeq.tryFindBack returns only element when singleton matches`` () =
3468+
let result =
3469+
AsyncSeq.singleton 42
3470+
|> AsyncSeq.tryFindBack (fun x -> x = 42)
3471+
|> Async.RunSynchronously
3472+
Assert.AreEqual(Some 42, result)
3473+
3474+
[<Test>]
3475+
let ``AsyncSeq.findBack returns last matching element`` () =
3476+
let result =
3477+
AsyncSeq.ofSeq [ 1; 2; 3; 4; 5 ]
3478+
|> AsyncSeq.findBack (fun x -> x < 4)
3479+
|> Async.RunSynchronously
3480+
Assert.AreEqual(3, result)
3481+
3482+
[<Test>]
3483+
let ``AsyncSeq.findBack raises KeyNotFoundException when no match`` () =
3484+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3485+
AsyncSeq.ofSeq [ 1; 2; 3 ] |> AsyncSeq.findBack (fun x -> x = 99) |> Async.RunSynchronously |> ignore)
3486+
|> ignore
3487+
3488+
[<Test>]
3489+
let ``AsyncSeq.tryFindBackAsync returns last matching element`` () =
3490+
let result =
3491+
AsyncSeq.ofSeq [ 1; 2; 3; 4; 5 ]
3492+
|> AsyncSeq.tryFindBackAsync (fun x -> async { return x % 2 = 0 })
3493+
|> Async.RunSynchronously
3494+
Assert.AreEqual(Some 4, result)
3495+
3496+
[<Test>]
3497+
let ``AsyncSeq.tryFindBackAsync returns None when no match`` () =
3498+
let result =
3499+
AsyncSeq.ofSeq [ 1; 3; 5 ]
3500+
|> AsyncSeq.tryFindBackAsync (fun x -> async { return x % 2 = 0 })
3501+
|> Async.RunSynchronously
3502+
Assert.AreEqual(None, result)
3503+
3504+
[<Test>]
3505+
let ``AsyncSeq.findBackAsync returns last matching element`` () =
3506+
let result =
3507+
AsyncSeq.ofSeq [ 10; 20; 30; 40 ]
3508+
|> AsyncSeq.findBackAsync (fun x -> async { return x < 35 })
3509+
|> Async.RunSynchronously
3510+
Assert.AreEqual(30, result)
3511+
3512+
[<Test>]
3513+
let ``AsyncSeq.findBackAsync raises KeyNotFoundException when no match`` () =
3514+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () ->
3515+
AsyncSeq.ofSeq [ 1; 2; 3 ]
3516+
|> AsyncSeq.findBackAsync (fun x -> async { return x = 99 })
3517+
|> Async.RunSynchronously |> ignore)
3518+
|> ignore
3519+
34403520
// ===== sortWith =====
34413521

34423522
[<Test>]

version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>4.11.0</Version>
3+
<Version>4.12.0</Version>
44
</PropertyGroup>
55
</Project>

0 commit comments

Comments
 (0)