Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/attrsets: add genAttrs' #270127

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/attrsets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,20 @@ rec {
f:
listToAttrs (map (n: nameValuePair n (f n)) names);

/* Generate an attribute set by mapping over a list of items
Taking two functions, one to apply for the name and one to
apply for the value of the attribute set.

Example:
genAttrs' [ {a = 2; b.c = "a" } {a = 1; b.c = "b"} ]
(i: i.b.c) (i: i.a)
Comment on lines +659 to +660
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't overload a and b, e.g.:

Suggested change
genAttrs' [ {a = 2; b.c = "a" } {a = 1; b.c = "b"} ]
(i: i.b.c) (i: i.a)
genAttrs' [ {p = 2; q.r = "a" } {p = 1; q.r = "b"} ]
(i: i.q.r) (i: i.p)

=> { a = 2; b = 1; }

Type:
genAttrs' :: [ a ] -> (a -> String) -> (a -> Any) -> AttrSet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A denotationally equivalent function would be

-genAttrs' :: [ a ] -> (a -> String) -> (a -> Any) -> AttrSet
+genAttrs' :: [ a ] -> (a -> { name :: String; value :: a}) -> AttrSet

However, operationally, the latter can use a let binding to share computation between the two attributes.
The prior can not share a variable that depends on the a.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet another alternative is

genAttrs' :: [ a ] -> (a -> Attrs) -> Attrs

This more compact, more powerful, and you can still read that a name and value are computed.

genAttrs' [ {a = 2; b.c = "a" } {a = 1; b.c = "b"} ]
  (i: { ${i.b.c} = i.a; })

while also allowing filtering behavior:

genAttrs' [ {a = 2; b.c = "a" } {a = 1; b.c = "b"} ]
  (i: optionalAttrs (i.a > 1) { ${i.b.c} = i.a; })

or multiple keys per item

genAttrs' [ {a = 2; b.c = "a" } {a = 1; b.c = "b"} ]
  (i: {
    ${i.b.c} = i.a;
    "${i.b.c}-squared" = i.a * i.a;
  })

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever genAttrs' ends up being, its documentation should refer to the other functions that are similar, and the other functions should refer back.
Some functions I'd think of

  • genAttrs
  • concatMapAttrs
  • listToAttrs

There's probably a more related function I'm not thinking of.

*/
genAttrs' = items: f: g:
listToAttrs (map (i: nameValuePair (f i) (g i)) items);

/* Check whether the argument is a derivation. Any set with
`{ type = "derivation"; }` counts as a derivation.
Expand Down
11 changes: 11 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,17 @@ runTests {
};
};

testGenAttrs = {
expr = attrsets.genAttrs [ "foo" "bar" ] (name: "${name}123");
expected = { foo = "foo123"; bar = "bar123"; };
};
testGenAttrsPrime = {
expr = attrsets.genAttrs' [ { foo = "a"; bar = "b"; } { foo = "c"; bar = "d"; } ] (x: x.foo) (x: "${toString x.foo}${toString x.bar}");
expected = {
a = "ab";
c = "cd";
};
};

testMergeAttrsListExample1 = {
expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
Expand Down