Merge pull request #298680 from gvolpe/lib/transposeMap

lib/attrsets: add mapCartesianProduct function

Changed files
+115 -45
lib
nixos
modules
services
x11
display-managers
tests
pkgs
by-name
so
solo5
data
fonts
junicode
icons
catppuccin-cursors
comixcursors
development
ocaml-modules
ocaml-freestanding
+43 -5
lib/attrsets.nix
···
let
inherit (builtins) head length;
-
inherit (lib.trivial) mergeAttrs warn;
inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName;
inherit (lib.lists) foldr foldl' concatMap elemAt all partition groupBy take foldl;
in
···
# Type
```
-
cartesianProductOfSets :: AttrSet -> [AttrSet]
```
# Examples
:::{.example}
-
## `lib.attrsets.cartesianProductOfSets` usage example
```nix
-
cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
=> [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
···
:::
*/
-
cartesianProductOfSets =
attrsOfLists:
foldl' (listOfAttrs: attrName:
concatMap (attrs:
···
) listOfAttrs
) [{}] (attrNames attrsOfLists);
/**
Utility function that creates a `{name, value}` pair as expected by `builtins.listToAttrs`.
···
# DEPRECATED
zip = warn
"lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith;
}
···
let
inherit (builtins) head length;
+
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
···
# Type
```
+
cartesianProduct :: AttrSet -> [AttrSet]
```
# Examples
:::{.example}
+
## `lib.attrsets.cartesianProduct` usage example
```nix
+
cartesianProduct { a = [ 1 2 ]; b = [ 10 20 ]; }
=> [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
···
:::
*/
+
cartesianProduct =
attrsOfLists:
foldl' (listOfAttrs: attrName:
concatMap (attrs:
···
) listOfAttrs
) [{}] (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`.
···
# 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;
}
+2 -2
lib/default.nix
···
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput
getBin getLib getDev getMan chooseDevOutputs zipWithNames zip
-
recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets
-
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
···
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput
getBin getLib getDev getMan chooseDevOutputs zipWithNames zip
+
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
+19 -3
lib/lists.nix
···
## `lib.lists.crossLists` usage example
```nix
-
crossLists (x:y: "${toString x}${toString y}") [[1 2] [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]);
/**
Remove duplicate elements from the `list`. O(n^2) complexity.
···
## `lib.lists.crossLists` usage example
```nix
+
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.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.
+35 -15
lib/tests/misc.nix
···
boolToString
callPackagesWith
callPackageWith
-
cartesianProductOfSets
cli
composeExtensions
composeManyExtensions
···
makeIncludePath
makeOverridable
mapAttrs
matchAttrs
mergeAttrs
meta
-
mkOption
mod
nameValuePair
optionalDrvAttr
···
expr = (builtins.tryEval expr).success;
expected = true;
};
-
testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr);
testSanitizeDerivationName = { name, expected }:
let
···
};
testToPrettyMultiline = {
-
expr = mapAttrs (const (generators.toPretty { })) rec {
list = [ 3 4 [ false ] ];
attrs = { foo = null; bar.foo = "baz"; };
newlinestring = "\n";
···
there
test'';
};
-
expected = rec {
list = ''
[
3
···
expected = "«foo»";
};
-
testToPlist =
-
let
-
deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
-
in {
expr = mapAttrs (const (generators.toPlist { })) {
value = {
-
nested.values = rec {
int = 42;
float = 0.1337;
bool = true;
···
};
testCartesianProductOfEmptySet = {
-
expr = cartesianProductOfSets {};
expected = [ {} ];
};
testCartesianProductOfOneSet = {
-
expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
expected = [ { a = 1; } { a = 2; } { a = 3; } ];
};
testCartesianProductOfTwoSets = {
-
expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
expected = [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
···
};
testCartesianProductOfTwoSetsWithOneEmpty = {
-
expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
expected = [ ];
};
testCartesianProductOfThreeSets = {
-
expr = cartesianProductOfSets {
a = [ 1 2 3 ];
b = [ 10 20 30 ];
c = [ 100 200 300 ];
···
{ a = 3; b = 30; c = 200; }
{ a = 3; b = 30; c = 300; }
];
};
# The example from the showAttrPath documentation
···
boolToString
callPackagesWith
callPackageWith
+
cartesianProduct
cli
composeExtensions
composeManyExtensions
···
makeIncludePath
makeOverridable
mapAttrs
+
mapCartesianProduct
matchAttrs
mergeAttrs
meta
mod
nameValuePair
optionalDrvAttr
···
expr = (builtins.tryEval expr).success;
expected = true;
};
testSanitizeDerivationName = { name, expected }:
let
···
};
testToPrettyMultiline = {
+
expr = mapAttrs (const (generators.toPretty { })) {
list = [ 3 4 [ false ] ];
attrs = { foo = null; bar.foo = "baz"; };
newlinestring = "\n";
···
there
test'';
};
+
expected = {
list = ''
[
3
···
expected = "«foo»";
};
+
testToPlist = {
expr = mapAttrs (const (generators.toPlist { })) {
value = {
+
nested.values = {
int = 42;
float = 0.1337;
bool = true;
···
};
testCartesianProductOfEmptySet = {
+
expr = cartesianProduct {};
expected = [ {} ];
};
testCartesianProductOfOneSet = {
+
expr = cartesianProduct { a = [ 1 2 3 ]; };
expected = [ { a = 1; } { a = 2; } { a = 3; } ];
};
testCartesianProductOfTwoSets = {
+
expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; };
expected = [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
···
};
testCartesianProductOfTwoSetsWithOneEmpty = {
+
expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; };
expected = [ ];
};
testCartesianProductOfThreeSets = {
+
expr = cartesianProduct {
a = [ 1 2 3 ];
b = [ 10 20 30 ];
c = [ 100 200 300 ];
···
{ a = 3; b = 30; c = 200; }
{ a = 3; b = 30; c = 300; }
];
+
};
+
+
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
+2 -2
nixos/modules/services/x11/display-managers/default.nix
···
in
# We will generate every possible pair of WM and DM.
concatLists (
-
builtins.map
({dm, wm}: let
sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
script = xsession dm wm;
···
providedSessions = [ sessionName ];
})
)
-
(cartesianProductOfSets { dm = dms; wm = wms; })
);
};
···
in
# We will generate every possible pair of WM and DM.
concatLists (
+
lib.mapCartesianProduct
({dm, wm}: let
sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
script = xsession dm wm;
···
providedSessions = [ sessionName ];
})
)
+
{ dm = dms; wm = wms; }
);
};
+1 -1
nixos/tests/predictable-interface-names.nix
···
let
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
-
testCombinations = pkgs.lib.cartesianProductOfSets {
predictable = [true false];
withNetworkd = [true false];
systemdStage1 = [true false];
···
let
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+
testCombinations = pkgs.lib.cartesianProduct {
predictable = [true false];
withNetworkd = [true false];
systemdStage1 = [true false];
+7 -8
pkgs/by-name/so/solo5/package.nix
···
runHook postCheck
'';
-
meta = {
description = "Sandboxed execution environment";
homepage = "https://github.com/solo5/solo5";
-
license = lib.licenses.isc;
-
maintainers = with lib.maintainers; [ ehmry ];
-
platforms = builtins.map ({arch, os}: "${arch}-${os}")
-
(lib.cartesianProductOfSets {
-
arch = [ "aarch64" "x86_64" ];
-
os = [ "freebsd" "genode" "linux" "openbsd" ];
-
});
};
}
···
runHook postCheck
'';
+
meta = with lib; {
description = "Sandboxed execution environment";
homepage = "https://github.com/solo5/solo5";
+
license = licenses.isc;
+
maintainers = [ maintainers.ehmry ];
+
platforms = mapCartesianProduct ({ arch, os }: "${arch}-${os}") {
+
arch = [ "aarch64" "x86_64" ];
+
os = [ "freebsd" "genode" "linux" "openbsd" ];
+
};
};
}
+3 -4
pkgs/data/fonts/junicode/tests.nix
···
'');
in
builtins.listToAttrs (
-
map
-
texTest
-
(lib.attrsets.cartesianProductOfSets {
tex = [ "xelatex" "lualatex" ];
fonttype = [ "ttf" "otf" ];
package = [ "junicode" ];
file = [ ./test.tex ];
-
})
++
[
(texTest {
···
'');
in
builtins.listToAttrs (
+
lib.mapCartesianProduct texTest
+
{
tex = [ "xelatex" "lualatex" ];
fonttype = [ "ttf" "otf" ];
package = [ "junicode" ];
file = [ ./test.tex ];
+
}
++
[
(texTest {
+1 -2
pkgs/data/icons/catppuccin-cursors/default.nix
···
palette = [ "Frappe" "Latte" "Macchiato" "Mocha" ];
color = [ "Blue" "Dark" "Flamingo" "Green" "Lavender" "Light" "Maroon" "Mauve" "Peach" "Pink" "Red" "Rosewater" "Sapphire" "Sky" "Teal" "Yellow" ];
};
-
product = lib.attrsets.cartesianProductOfSets dimensions;
variantName = { palette, color }: (lib.strings.toLower palette) + color;
-
variants = map variantName product;
in
stdenvNoCC.mkDerivation rec {
pname = "catppuccin-cursors";
···
palette = [ "Frappe" "Latte" "Macchiato" "Mocha" ];
color = [ "Blue" "Dark" "Flamingo" "Green" "Lavender" "Light" "Maroon" "Mauve" "Peach" "Pink" "Red" "Rosewater" "Sapphire" "Sky" "Teal" "Yellow" ];
};
variantName = { palette, color }: (lib.strings.toLower palette) + color;
+
variants = lib.mapCartesianProduct variantName dimensions;
in
stdenvNoCC.mkDerivation rec {
pname = "catppuccin-cursors";
+1 -2
pkgs/data/icons/comixcursors/default.nix
···
thickness = [ "" "Slim_" ]; # Thick or slim edges.
handedness = [ "" "LH_" ]; # Right- or left-handed.
};
-
product = lib.cartesianProductOfSets dimensions;
variantName =
{ color, opacity, thickness, handedness }:
"${handedness}${opacity}${thickness}${color}";
variants =
# (The order of this list is already good looking enough to show in the
# meta.longDescription.)
-
map variantName product;
in
stdenvNoCC.mkDerivation rec {
pname = "comixcursors";
···
thickness = [ "" "Slim_" ]; # Thick or slim edges.
handedness = [ "" "LH_" ]; # Right- or left-handed.
};
variantName =
{ color, opacity, thickness, handedness }:
"${handedness}${opacity}${thickness}${color}";
variants =
# (The order of this list is already good looking enough to show in the
# meta.longDescription.)
+
lib.mapCartesianProduct variantName dimensions;
in
stdenvNoCC.mkDerivation rec {
pname = "comixcursors";
+1 -1
pkgs/development/ocaml-modules/ocaml-freestanding/default.nix
···
maintainers = [ maintainers.sternenseemann ];
homepage = "https://github.com/mirage/ocaml-freestanding";
platforms = builtins.map ({ arch, os }: "${arch}-${os}")
-
(cartesianProductOfSets {
arch = [ "aarch64" "x86_64" ];
os = [ "linux" ];
} ++ [
···
maintainers = [ maintainers.sternenseemann ];
homepage = "https://github.com/mirage/ocaml-freestanding";
platforms = builtins.map ({ arch, os }: "${arch}-${os}")
+
(cartesianProduct {
arch = [ "aarch64" "x86_64" ];
os = [ "linux" ];
} ++ [