summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorhsjobeki <hsjobeki+github@gmail.com>2023-02-28 11:04:19 +0100
committerhsjobeki <hsjobeki+github@gmail.com>2023-03-11 10:42:00 +0100
commit15a8d05ba592355f37442743bb3ce9fe55b72911 (patch)
tree98a5da74ced314a9f40f49b4115a89fdd6b38dfc /lib
parent624432c25b2044dc291608cd204a7dee5275089b (diff)
init: lib.foldlAttrs
- provide comprehensive example
- add unit test
Diffstat (limited to 'lib')
-rw-r--r--lib/attrsets.nix60
-rw-r--r--lib/default.nix2
-rw-r--r--lib/tests/misc.nix31
3 files changed, 92 insertions, 1 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix
index 30952651adf40..d9a6eab0603ed 100644
--- a/lib/attrsets.nix
+++ b/lib/attrsets.nix
@@ -333,6 +333,66 @@ rec {
       ) (attrNames set)
     );
 
+   /*
+    Like builtins.foldl' but for attribute sets.
+    Iterates over every name-value pair in the given attribute set.
+    The result of the callback function is often called `acc` for accumulator. It is passed between callbacks from left to right and the final `acc` is the return value of `foldlAttrs`.
+
+    Attention:
+      There is a completely different function
+      `lib.foldAttrs`
+      which has nothing to do with this function, despite the similar name.
+
+    Example:
+      foldlAttrs
+        (acc: name: value: {
+          sum = acc.sum + value;
+          names = acc.names ++ [name];
+        })
+        { sum = 0; names = []; }
+        {
+          foo = 1;
+          bar = 10;
+        }
+      ->
+        {
+          sum = 11;
+          names = ["bar" "foo"];
+        }
+
+      foldlAttrs
+        (throw "function not needed")
+        123
+        {};
+      ->
+        123
+
+      foldlAttrs
+        (_: _: v: v)
+        (throw "initial accumulator not needed")
+        { z = 3; a = 2; };
+      ->
+        3
+
+      The accumulator doesn't have to be an attrset.
+      It can be as simple as a number or string.
+
+      foldlAttrs
+        (acc: _: v: acc * 10 + v)
+        1
+        { z = 1; a = 2; };
+      ->
+        121
+
+    Type:
+      foldlAttrs :: ( a -> String -> b -> a ) -> a -> { ... :: b } -> a
+  */
+  foldlAttrs = f: init: set:
+    foldl'
+      (acc: name: f acc name set.${name})
+      init
+      (attrNames set);
+
   /* Apply fold functions to values grouped by key.
 
      Example:
diff --git a/lib/default.nix b/lib/default.nix
index 85303e0a5abfb..0424db36b2e99 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -78,7 +78,7 @@ let
       composeManyExtensions makeExtensible makeExtensibleWithCustomName;
     inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
       getAttrFromPath attrVals attrValues getAttrs catAttrs filterAttrs
-      filterAttrsRecursive foldAttrs collect nameValuePair mapAttrs
+      filterAttrsRecursive foldlAttrs foldAttrs collect nameValuePair mapAttrs
       mapAttrs' mapAttrsToList concatMapAttrs mapAttrsRecursive mapAttrsRecursiveCond
       genAttrs isDerivation toDerivation optionalAttrs
       zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 07d04f5356c7b..baa382f3e589c 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -533,6 +533,37 @@ runTests {
     };
   };
 
+  # code from example
+  testFoldlAttrs = {
+    expr = {
+      example = foldlAttrs
+        (acc: name: value: {
+          sum = acc.sum + value;
+          names = acc.names ++ [ name ];
+        })
+        { sum = 0; names = [ ]; }
+        {
+          foo = 1;
+          bar = 10;
+        };
+      # should just return the initial value
+      emptySet = foldlAttrs (throw "function not needed") 123 { };
+      # should just evaluate to the last value
+      accNotNeeded = foldlAttrs (_acc: _name: v: v) (throw "accumulator not needed") { z = 3; a = 2; };
+      # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string
+      trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; };
+    };
+    expected = {
+      example = {
+        sum = 11;
+        names = [ "bar" "foo" ];
+      };
+      emptySet = 123;
+      accNotNeeded = 3;
+      trivialAcc = 121;
+    };
+  };
+
   # code from the example
   testRecursiveUpdateUntil = {
     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {