From b931295e209de9d8d93d95dc5ace8fa3ca112535 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 01:19:31 +0000 Subject: [PATCH 1/2] feat: add exists, forall, choose to LazyList module LazyList was missing three common collection functions that are present in FSharp.Collections.List and Seq: - exists: returns true if any element satisfies the predicate (short-circuits) - forall: returns true if all elements satisfy the predicate (short-circuits) - choose: applies an option-returning function; lazily yields Some values exists and forall are strict (traverse the list up to the first match/mismatch). choose follows the same lazy evaluation pattern as filter: evaluation is deferred until the returned list is consumed. Both LazyList.fs and LazyList.fsi updated; 9 new tests added, all passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharpx.Collections/LazyList.fs | 21 +++++++ src/FSharpx.Collections/LazyList.fsi | 10 ++++ .../LazyListTests.fs | 55 ++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/FSharpx.Collections/LazyList.fs b/src/FSharpx.Collections/LazyList.fs index ab0c935f..9d7803bc 100644 --- a/src/FSharpx.Collections/LazyList.fs +++ b/src/FSharpx.Collections/LazyList.fs @@ -184,6 +184,27 @@ module LazyList = | Some a -> a | None -> indexNotFound() + let rec exists p s = + match getCell s with + | CellCons(a, b) -> if p a then true else exists p b + | CellEmpty -> false + + let rec forall p s = + match getCell s with + | CellCons(a, b) -> if not(p a) then false else forall p b + | CellEmpty -> true + + let rec choose f s1 = + lzy(fun () -> choosec f s1) + + and choosec f s1 = + match getCell s1 with + | CellCons(a, b) -> + match f a with + | Some v -> consc v (choose f b) + | None -> choosec f b + | CellEmpty -> CellEmpty + let rec scan f acc s1 = lzy(fun () -> match getCell s1 with diff --git a/src/FSharpx.Collections/LazyList.fsi b/src/FSharpx.Collections/LazyList.fsi index 8c99f156..3540c24d 100644 --- a/src/FSharpx.Collections/LazyList.fsi +++ b/src/FSharpx.Collections/LazyList.fsi @@ -123,6 +123,16 @@ module LazyList = /// Raise KeyNotFoundException if no such element exists. val find: predicate: ('T -> bool) -> source: LazyList<'T> -> 'T + ///O(n), worst case. Returns true if any element of the list satisfies the given predicate. + val exists: predicate: ('T -> bool) -> source: LazyList<'T> -> bool + + ///O(n), worst case. Returns true if all elements of the list satisfy the given predicate. + val forall: predicate: ('T -> bool) -> source: LazyList<'T> -> bool + + ///O(1). Returns a new list consisting of the results of applying the given function to each element, + /// keeping only the values where the function returns Some. + val choose: mapping: ('T -> 'U option) -> source: LazyList<'T> -> LazyList<'U> + ///O(1). Evaluates to the list that contains no items [] val empty<'T> : LazyList<'T> diff --git a/tests/FSharpx.Collections.Tests/LazyListTests.fs b/tests/FSharpx.Collections.Tests/LazyListTests.fs index abdd1bdb..640b6d58 100644 --- a/tests/FSharpx.Collections.Tests/LazyListTests.fs +++ b/tests/FSharpx.Collections.Tests/LazyListTests.fs @@ -532,4 +532,57 @@ module LazyList = } test "scan 3" { Expect.equal "scan" [ 0; 1; 3 ] (LazyList.scan (+) 0 (LazyList.ofList [ 1; 2 ]) |> LazyList.toList) } - test "scan 0" { Expect.equal "scan" [ 0 ] (LazyList.scan (+) 0 (LazyList.ofList []) |> LazyList.toList) } ] + test "scan 0" { Expect.equal "scan" [ 0 ] (LazyList.scan (+) 0 (LazyList.ofList []) |> LazyList.toList) } + + test "exists returns true when element satisfies predicate" { + let ll = LazyList.ofList [ 1; 2; 3; 4; 5 ] + Expect.isTrue "exists" (LazyList.exists (fun x -> x = 3) ll) + } + + test "exists returns false when no element satisfies predicate" { + let ll = LazyList.ofList [ 1; 2; 3; 4; 5 ] + Expect.isFalse "exists" (LazyList.exists (fun x -> x = 6) ll) + } + + test "exists empty returns false" { Expect.isFalse "exists empty" (LazyList.exists (fun _ -> true) LazyList.empty) } + + test "forall returns true when all elements satisfy predicate" { + let ll = LazyList.ofList [ 2; 4; 6; 8 ] + Expect.isTrue "forall" (LazyList.forall (fun x -> x % 2 = 0) ll) + } + + test "forall returns false when some element does not satisfy predicate" { + let ll = LazyList.ofList [ 2; 3; 4 ] + Expect.isFalse "forall" (LazyList.forall (fun x -> x % 2 = 0) ll) + } + + test "forall empty returns true" { Expect.isTrue "forall empty" (LazyList.forall (fun _ -> false) LazyList.empty) } + + test "choose keeps Some values in order" { + let ll = LazyList.ofList [ 1; 2; 3; 4; 5 ] + let result = LazyList.choose (fun x -> if x % 2 = 0 then Some(x * 10) else None) ll + Expect.equal "choose" [ 20; 40 ] (LazyList.toList result) + } + + test "choose all None returns empty" { + let ll = LazyList.ofList [ 1; 3; 5 ] + let result = LazyList.choose (fun _ -> None) ll + Expect.isTrue "choose none" (LazyList.isEmpty result) + } + + test "choose lazy: only evaluates up to consumed elements" { + let counter = ref 0 + + let ll = + LazyList.ofSeq( + seq { + for i in 1..10 do + incr counter + yield i + } + ) + + let chosen = LazyList.choose (fun x -> if x % 2 = 0 then Some x else None) ll + let first = LazyList.head chosen + Expect.equal "choose lazy first" 2 first + } ] From f8bfeb096131baee0258514862ab4f8ec9ff2fcd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 01:25:24 +0000 Subject: [PATCH 2/2] ci: trigger checks