From d0f2da214badd4cded6fb00e97af4588da49c0d0 Mon Sep 17 00:00:00 2001 From: sternenseemann Date: Sun, 16 Oct 2022 01:32:55 +0200 Subject: [PATCH] primops: make nature of foldl' strictness clearer * Clarify the documentation of foldl': That the arguments are forced before application (?) of `op` is necessarily true. What is important to stress is that we force every application of `op`, even when the value turns out to be unused. * Move the example before the comment about strictness to make it less confusing: It is a general example and doesn't really showcase anything about foldl' strictness. * Add test cases which nail down aspects of foldl' strictness: * The initial accumulator value is not forced unconditionally. * Applications of op are forced. * The list elements are not forced unconditionally. --- src/libexpr/primops.cc | 6 +++--- .../lang/eval-fail-foldlStrict-strict-op-application.nix | 5 +++++ tests/lang/eval-okay-foldlStrict-lazy-elements.exp | 1 + tests/lang/eval-okay-foldlStrict-lazy-elements.nix | 9 +++++++++ .../eval-okay-foldlStrict-lazy-initial-accumulator.exp | 1 + .../eval-okay-foldlStrict-lazy-initial-accumulator.nix | 6 ++++++ 6 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 tests/lang/eval-fail-foldlStrict-strict-op-application.nix create mode 100644 tests/lang/eval-okay-foldlStrict-lazy-elements.exp create mode 100644 tests/lang/eval-okay-foldlStrict-lazy-elements.nix create mode 100644 tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp create mode 100644 tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 28b998474..ba4b7c67a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2908,9 +2908,9 @@ static RegisterPrimOp primop_foldlStrict({ .doc = R"( Reduce a list by applying a binary operator, from left to right, e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2) - ...`. The operator is applied strictly, i.e., its arguments are - evaluated first. For example, `foldl' (x: y: x + y) 0 [1 2 3]` - evaluates to 6. + ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6. + The return value of each application of `op` is evaluated immediately, + even for intermediate values. )", .fun = prim_foldlStrict, }); diff --git a/tests/lang/eval-fail-foldlStrict-strict-op-application.nix b/tests/lang/eval-fail-foldlStrict-strict-op-application.nix new file mode 100644 index 000000000..1620cc76e --- /dev/null +++ b/tests/lang/eval-fail-foldlStrict-strict-op-application.nix @@ -0,0 +1,5 @@ +# Tests that the result of applying op is forced even if the value is never used +builtins.foldl' + (_: f: f null) + null + [ (_: throw "Not the final value, but is still forced!") (_: 23) ] diff --git a/tests/lang/eval-okay-foldlStrict-lazy-elements.exp b/tests/lang/eval-okay-foldlStrict-lazy-elements.exp new file mode 100644 index 000000000..d81cc0710 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-elements.exp @@ -0,0 +1 @@ +42 diff --git a/tests/lang/eval-okay-foldlStrict-lazy-elements.nix b/tests/lang/eval-okay-foldlStrict-lazy-elements.nix new file mode 100644 index 000000000..c666e07f3 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-elements.nix @@ -0,0 +1,9 @@ +# Tests that the rhs argument of op is not forced unconditionally +let + lst = builtins.foldl' + (acc: x: acc ++ [ x ]) + [ ] + [ 42 (throw "this shouldn't be evaluated") ]; +in + +builtins.head lst diff --git a/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp new file mode 100644 index 000000000..d81cc0710 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp @@ -0,0 +1 @@ +42 diff --git a/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix new file mode 100644 index 000000000..abcd5366a --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix @@ -0,0 +1,6 @@ +# Checks that the nul value for the accumulator is not forced unconditionally. +# Some languages provide a foldl' that is strict in this argument, but Nix does not. +builtins.foldl' + (_: x: x) + (throw "This is never forced") + [ "but the results of applying op are" 42 ]