Loading lib/lists.nix +32 −2 Original line number Diff line number Diff line Loading @@ -198,8 +198,38 @@ rec { default: # Input list list: let found = filter pred list; in if found == [] then default else head found; 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 = 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 elemAt list resultIndex; /* Return true if function `pred` returns true for at least one element of `list`. Loading lib/tests/misc.nix +40 −0 Original line number Diff line number Diff line Loading @@ -518,6 +518,46 @@ runTests { expected = false; }; testFindFirstExample1 = { expr = findFirst (x: x > 3) 7 [ 1 6 4 ]; expected = 6; }; testFindFirstExample2 = { expr = findFirst (x: x > 9) 7 [ 1 6 4 ]; expected = 7; }; testFindFirstEmpty = { expr = findFirst (abort "when the list is empty, the predicate is not needed") null []; expected = null; }; testFindFirstSingleMatch = { expr = findFirst (x: x == 5) null [ 5 ]; expected = 5; }; testFindFirstSingleDefault = { expr = findFirst (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ]; expected = null; }; testFindFirstNone = { expr = builtins.tryEval (findFirst (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]); expected = { success = false; value = false; }; }; # Makes sure that the implementation doesn't cause a stack overflow testFindFirstBig = { expr = findFirst (x: x == 1000000) null (range 0 1000000); expected = 1000000; }; testFindFirstLazy = { expr = findFirst (x: x == 1) 7 [ 1 (abort "list elements after the match must not be evaluated") ]; expected = 1; }; # ATTRSETS Loading Loading
lib/lists.nix +32 −2 Original line number Diff line number Diff line Loading @@ -198,8 +198,38 @@ rec { default: # Input list list: let found = filter pred list; in if found == [] then default else head found; 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 = 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 elemAt list resultIndex; /* Return true if function `pred` returns true for at least one element of `list`. Loading
lib/tests/misc.nix +40 −0 Original line number Diff line number Diff line Loading @@ -518,6 +518,46 @@ runTests { expected = false; }; testFindFirstExample1 = { expr = findFirst (x: x > 3) 7 [ 1 6 4 ]; expected = 6; }; testFindFirstExample2 = { expr = findFirst (x: x > 9) 7 [ 1 6 4 ]; expected = 7; }; testFindFirstEmpty = { expr = findFirst (abort "when the list is empty, the predicate is not needed") null []; expected = null; }; testFindFirstSingleMatch = { expr = findFirst (x: x == 5) null [ 5 ]; expected = 5; }; testFindFirstSingleDefault = { expr = findFirst (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ]; expected = null; }; testFindFirstNone = { expr = builtins.tryEval (findFirst (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]); expected = { success = false; value = false; }; }; # Makes sure that the implementation doesn't cause a stack overflow testFindFirstBig = { expr = findFirst (x: x == 1000000) null (range 0 1000000); expected = 1000000; }; testFindFirstLazy = { expr = findFirst (x: x == 1) 7 [ 1 (abort "list elements after the match must not be evaluated") ]; expected = 1; }; # ATTRSETS Loading