zipAttrsWith
lib.attrsets.zipAttrsWith
Nixpkgs manual
Merge sets of attributes and use the function f to merge attribute values.
Like lib.attrsets.zipAttrsWithNames with all key names are passed for names.
Implementation note: Common names appear multiple times in the list of
names, hopefully this does not affect the system because the maximal
laziness avoid computing twice the same expression and listToAttrs does
not care about duplicated attribute names.
Type
zipAttrsWith :: (String -> [a] -> b) -> [{ [String] :: a }] -> { [String] :: b }
Examples
lib.attrsets.zipAttrsWith usage example
zipAttrsWith (name: values: values) [{a = "x";} {a = "y"; b = "z";}]
=> { a = ["x" "y"]; b = ["z"]; }
Nix manual
Takes 2 arguments
f, list
Transpose a list of attribute sets into an attribute set of lists,
then apply mapAttrs.
f receives two arguments: the attribute name and a non-empty
list of all values encountered for that attribute name.
The result is an attribute set where the attribute names are the
union of the attribute names in each element of list. The attribute
values are the return values of f.
builtins.zipAttrsWith
(name: values: { inherit name values; })
[ { a = "x"; } { a = "y"; b = "z"; } ]
evaluates to
{
a = { name = "a"; values = [ "x" "y" ]; };
b = { name = "b"; values = [ "z" ]; };
}
Time Complexity
O(N * log k) where:
N = total attributes across all sets k = number of unique keys across all sets
Noogle detected
Implementation
This function is implemented in c++ and is part of the native nix runtime.
static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
// we will first count how many values are present for each given key.
// we then allocate a single attrset and pre-populate it with lists of
// appropriate sizes, stash the pointers to the list elements of each,
// and populate the lists. after that we replace the list in the every
// attribute with the merge function application. this way we need not
// use (slightly slower) temporary storage the GC does not know about.
struct Item
{
size_t size = 0;
size_t pos = 0;
std::optional<ListBuilder> list;
};
std::map<Symbol, Item, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Item>>> attrsSeen;
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith");
const auto listItems = args[1]->listView();
for (auto & vElem : listItems) {
state.forceAttrs(
*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs())
attrsSeen.try_emplace(attr.name).first->second.size++;
}
for (auto & [sym, elem] : attrsSeen)
elem.list.emplace(state.buildList(elem.size));
for (auto & vElem : listItems) {
for (auto & attr : *vElem->attrs()) {
auto & item = attrsSeen.at(attr.name);
(*item.list)[item.pos++] = attr.value;
}
}
auto attrs = state.buildBindings(attrsSeen.size());
for (auto & [sym, elem] : attrsSeen) {
auto name = Value::toPtr(state.symbols[sym]);
auto call1 = state.allocValue();
call1->mkApp(args[0], name);
auto call2 = state.allocValue();
auto arg = state.allocValue();
arg->mkList(*elem.list);
call2->mkApp(call1, arg);
attrs.insert(sym, call2);
}
v.mkAttrs(attrs.alreadySorted());
}
Implementation
The following is the current implementation of this function.
zipAttrsWith =
builtins.zipAttrsWith or (f: sets: zipAttrsWithNames (concatMap attrNames sets) f sets);