From e5c8a66babfc7249329595865718cce4808c7022 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 22:00:00 +1000 Subject: [PATCH] list indexing functions --- nib/std/lists.nix | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/nib/std/lists.nix b/nib/std/lists.nix index 6019018..1cc8982 100644 --- a/nib/std/lists.nix +++ b/nib/std/lists.nix @@ -1,4 +1,4 @@ -rec { +{}: rec { foldl = op: nul: list: let foldl' = n: if n == -1 @@ -8,4 +8,47 @@ rec { foldl' (builtins.length list - 1); crossLists = f: foldl (fs: args: builtins.concatMap (f: map f args) fs) [f]; + + findFirstIndex = pred: default: list: let + # A naive recursive implementation would be much simpler, but + # would also overflow the evaluator stack. We use `foldl'` as a workaround + # because it reuses the same stack space, evaluating the function for one + # element after another. We can't return early, so this means that we + # sacrifice early cutoff, but that appears to be an acceptable cost. A + # clever scheme with "exponential search" is possible, but appears over- + # engineered for now. See https://github.com/NixOS/nixpkgs/pull/235267 + # Invariant: + # - if index < 0 then el == elemAt list (- index - 1) and all elements before el didn't satisfy pred + # - if index >= 0 then pred (elemAt list index) and all elements before (elemAt list index) didn't satisfy pred + # + # We start with index -1 and the 0'th element of the list, which satisfies the invariant + resultIndex = + builtins.foldl' ( + index: el: + if index < 0 + then + # No match yet before the current index, we need to check the element + if pred el + then + # We have a match! Turn it into the actual index to prevent future iterations from modifying it + -index - 1 + else + # Still no match, update the index to the next element (we're counting down, so minus one) + index - 1 + else + # There's already a match, propagate the index without evaluating anything + index + ) (-1) + list; + in + if resultIndex < 0 + then default + else resultIndex; + + findFirst = pred: default: list: let + index = findFirstIndex pred null list; + in + if index == null + then default + else builtins.elemAt list index; }