diff --git a/lib/attrsets.nix b/lib/attrsets.nix index 49d311ed3..83f8d0f34 100644 --- a/lib/attrsets.nix +++ b/lib/attrsets.nix @@ -5,7 +5,7 @@ let inherit (builtins) head length; - inherit (lib.trivial) mergeAttrs warn; + inherit (lib.trivial) isInOldestRelease mergeAttrs warn warnIf; inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName; inherit (lib.lists) foldr foldl' concatMap elemAt all partition groupBy take foldl; in @@ -885,15 +885,15 @@ rec { # Type ``` - cartesianProductOfSets :: AttrSet -> [AttrSet] + cartesianProduct :: AttrSet -> [AttrSet] ``` # Examples :::{.example} - ## `lib.attrsets.cartesianProductOfSets` usage example + ## `lib.attrsets.cartesianProduct` usage example ```nix - cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; } + cartesianProduct { a = [ 1 2 ]; b = [ 10 20 ]; } => [ { a = 1; b = 10; } { a = 1; b = 20; } @@ -904,7 +904,7 @@ rec { ::: */ - cartesianProductOfSets = + cartesianProduct = attrsOfLists: foldl' (listOfAttrs: attrName: concatMap (attrs: @@ -913,6 +913,40 @@ rec { ) [{}] (attrNames attrsOfLists); + /** + Return the result of function f applied to the cartesian product of attribute set value combinations. + Equivalent to using cartesianProduct followed by map. + + # Inputs + + `f` + + : A function, given an attribute set, it returns a new value. + + `attrsOfLists` + + : Attribute set with attributes that are lists of values + + # Type + + ``` + mapCartesianProduct :: (AttrSet -> a) -> AttrSet -> [a] + ``` + + # Examples + :::{.example} + ## `lib.attrsets.mapCartesianProduct` usage example + + ```nix + mapCartesianProduct ({a, b}: "${a}-${b}") { a = [ "1" "2" ]; b = [ "3" "4" ]; } + => [ "1-3" "1-4" "2-3" "2-4" ] + ``` + + ::: + + */ + mapCartesianProduct = f: attrsOfLists: map f (cartesianProduct attrsOfLists); + /** Utility function that creates a `{name, value}` pair as expected by `builtins.listToAttrs`. @@ -1999,4 +2033,8 @@ rec { # DEPRECATED zip = warn "lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith; + + # DEPRECATED + cartesianProductOfSets = warnIf (isInOldestRelease 2405) + "lib.cartesianProductOfSets is a deprecated alias of lib.cartesianProduct." cartesianProduct; } diff --git a/lib/default.nix b/lib/default.nix index 21e4bab2b..486d412fb 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -86,8 +86,8 @@ let zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput getBin getLib getDev getMan chooseDevOutputs zipWithNames zip - recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets - updateManyAttrsByPath; + recurseIntoAttrs dontRecurseIntoAttrs cartesianProduct cartesianProductOfSets + mapCartesianProduct updateManyAttrsByPath; inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1 concatMap flatten remove findSingle findFirst any all count optional optionals toList range replicate partition zipListsWith zipLists diff --git a/lib/lists.nix b/lib/lists.nix index c162f9212..28fa277b2 100644 --- a/lib/lists.nix +++ b/lib/lists.nix @@ -1688,16 +1688,32 @@ rec { ## `lib.lists.crossLists` usage example ```nix - crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]] + crossLists (x: y: "${toString x}${toString y}") [[1 2] [3 4]] => [ "13" "14" "23" "24" ] ``` + The following function call is equivalent to the one deprecated above: + + ```nix + mapCartesianProduct (x: "${toString x.a}${toString x.b}") { a = [1 2]; b = [3 4]; } + => [ "13" "14" "23" "24" ] + ``` ::: */ crossLists = warn - "lib.crossLists is deprecated, use lib.cartesianProductOfSets instead." - (f: foldl (fs: args: concatMap (f: map f args) fs) [f]); + ''lib.crossLists is deprecated, use lib.mapCartesianProduct instead. + For example, the following function call: + + nix-repl> lib.crossLists (x: y: x+y) [[1 2] [3 4]] + [ 4 5 5 6 ] + + Can now be replaced by the following one: + + nix-repl> lib.mapCartesianProduct ({x,y}: x+y) { x = [1 2]; y = [3 4]; } + [ 4 5 5 6 ] + '' + (f: foldl (fs: args: concatMap (f: map f args) fs) [f]); /** Remove duplicate elements from the `list`. O(n^2) complexity. diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index accceb4dd..cf4a185c1 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -33,7 +33,7 @@ let boolToString callPackagesWith callPackageWith - cartesianProductOfSets + cartesianProduct cli composeExtensions composeManyExtensions @@ -71,10 +71,10 @@ let makeIncludePath makeOverridable mapAttrs + mapCartesianProduct matchAttrs mergeAttrs meta - mkOption mod nameValuePair optionalDrvAttr @@ -117,7 +117,6 @@ let expr = (builtins.tryEval expr).success; expected = true; }; - testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr); testSanitizeDerivationName = { name, expected }: let @@ -1415,7 +1414,7 @@ runTests { }; testToPrettyMultiline = { - expr = mapAttrs (const (generators.toPretty { })) rec { + expr = mapAttrs (const (generators.toPretty { })) { list = [ 3 4 [ false ] ]; attrs = { foo = null; bar.foo = "baz"; }; newlinestring = "\n"; @@ -1429,7 +1428,7 @@ runTests { there test''; }; - expected = rec { + expected = { list = '' [ 3 @@ -1467,13 +1466,10 @@ runTests { expected = "«foo»"; }; - testToPlist = - let - deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; }; - in { + testToPlist = { expr = mapAttrs (const (generators.toPlist { })) { value = { - nested.values = rec { + nested.values = { int = 42; float = 0.1337; bool = true; @@ -1686,17 +1682,17 @@ runTests { }; testCartesianProductOfEmptySet = { - expr = cartesianProductOfSets {}; + expr = cartesianProduct {}; expected = [ {} ]; }; testCartesianProductOfOneSet = { - expr = cartesianProductOfSets { a = [ 1 2 3 ]; }; + expr = cartesianProduct { a = [ 1 2 3 ]; }; expected = [ { a = 1; } { a = 2; } { a = 3; } ]; }; testCartesianProductOfTwoSets = { - expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; }; + expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; }; expected = [ { a = 1; b = 10; } { a = 1; b = 20; } @@ -1704,12 +1700,12 @@ runTests { }; testCartesianProductOfTwoSetsWithOneEmpty = { - expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; }; + expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; }; expected = [ ]; }; testCartesianProductOfThreeSets = { - expr = cartesianProductOfSets { + expr = cartesianProduct { a = [ 1 2 3 ]; b = [ 10 20 30 ]; c = [ 100 200 300 ]; @@ -1753,6 +1749,30 @@ runTests { ]; }; + testMapCartesianProductOfOneSet = { + expr = mapCartesianProduct ({a}: a * 2) { a = [ 1 2 3 ]; }; + expected = [ 2 4 6 ]; + }; + + testMapCartesianProductOfTwoSets = { + expr = mapCartesianProduct ({a,b}: a + b) { a = [ 1 ]; b = [ 10 20 ]; }; + expected = [ 11 21 ]; + }; + + testMapCartesianProcutOfTwoSetsWithOneEmpty = { + expr = mapCartesianProduct (x: x.a + x.b) { a = [ ]; b = [ 10 20 ]; }; + expected = [ ]; + }; + + testMapCartesianProductOfThreeSets = { + expr = mapCartesianProduct ({a,b,c}: a + b + c) { + a = [ 1 2 3 ]; + b = [ 10 20 30 ]; + c = [ 100 200 300 ]; + }; + expected = [ 111 211 311 121 221 321 131 231 331 112 212 312 122 222 322 132 232 332 113 213 313 123 223 323 133 233 333 ]; + }; + # The example from the showAttrPath documentation testShowAttrPathExample = { expr = showAttrPath [ "foo" "10" "bar" ];