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.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
```
-
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; }
···
:::
*/
-
cartesianProductOfSets =
+
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;
+
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]]
+
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.
+35 -15
lib/tests/misc.nix
···
boolToString
callPackagesWith
callPackageWith
-
cartesianProductOfSets
+
cartesianProduct
cli
composeExtensions
composeManyExtensions
···
makeIncludePath
makeOverridable
mapAttrs
+
mapCartesianProduct
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 {
+
expr = mapAttrs (const (generators.toPretty { })) {
list = [ 3 4 [ false ] ];
attrs = { foo = null; bar.foo = "baz"; };
newlinestring = "\n";
···
there
test'';
};
-
expected = rec {
+
expected = {
list = ''
···
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;
···
};
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; }
···
};
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 ];
···
{ 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
+
lib.mapCartesianProduct
({dm, wm}: let
sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
script = xsession dm wm;
···
providedSessions = [ sessionName ];
})
)
-
(cartesianProductOfSets { dm = dms; wm = wms; })
+
{ 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 {
+
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 = {
+
meta = with lib; {
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" ];
-
});
+
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 {
+
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;
+
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;
+
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 {
+
(cartesianProduct {
arch = [ "aarch64" "x86_64" ];
os = [ "linux" ];
} ++ [