Skip to content

Instantly share code, notes, and snippets.

@blaggacao
Last active October 3, 2021 16:37
Show Gist options
  • Save blaggacao/050483417bcab6a5cd126152b0187314 to your computer and use it in GitHub Desktop.
Save blaggacao/050483417bcab6a5cd126152b0187314 to your computer and use it in GitHub Desktop.
merge-overlay
{ description = "A mini merge DSL for data overlays";
inputs.nixlib.url = "github:nix-community/nixpkgs.lib";
outputs = { self, nixlib }: let
# Incrementailty of the Data Spine
# --------------------------------
# Te reduce mental complexity in merging chains,
# we must ensure that the data spine of the left
# hand side is not destructively modified.
#
# This means, tha just as the keys of an attribute
# set cannot be removed through a merge operation,
# we also must ensure that no array element can
# be removed either.
#
# In this reasoning, the complex types _arrays_
# and _attribute sets_ represent the data spine. And
# while individual simple type mergres necesarily
# destroy information, the spine itself is not
# allowed to be transfromed itself destructively.
mergeAt = here: lhs: rhs:
let
inherit (builtins) isAttrs head tail typeOf concatStringsSep;
inherit (nixlib.lib) zipAttrsWith isList isFunction;
f = attrPath:
zipAttrsWith (n: values:
let
here' = attrPath ++ [ n ];
rhs' = head values;
lhs' = head (tail values);
isSingleton = tail values == [ ];
in
if isSingleton then head values
else if !(isAttrs lhs' && isAttrs rhs')
then
if (typeOf lhs') != (typeOf rhs') && !(isList lhs' && isFunction rhs')
then abort "rigt-hand-side must be of the same type as left-hand-side at '${concatStringsSep ''.'' here'}'"
else if isList lhs' && isList rhs'
then abort "rigt-hand-side list is not allowed to override left-hand-side list, this would break incrementality of the data spine. Use one of the array merge functions instead at '${concatStringsSep ''.'' here'}'"
# array function merge
else if isList lhs' && isFunction rhs' then rhs' lhs' here'
else rhs'
else f here' values
);
in f here [ rhs lhs ];
in {
merge = mergeAt [ ];
append = new: orig: here: let
inherit (builtins) isList typeOf concatStringsSep;
inherit (nixlib.lib) assertMsg;
in
assert assertMsg (isList new) "appending array merge: right-hand-side must be a list, got: ${typeOf new} at '${concatStringsSep ''.'' here}'";
orig ++ new;
update = indices: updates: orig: here: let
inherit (builtins) isList all isInt length typeOf listToAttrs elemAt hasAttr concatStringsSep;
inherit (nixlib.lib) zipListsWith imap0 assertMsg;
in
assert assertMsg (isList indices && all (i: isInt i) indices)
"updating array merge: first argument must be a list of indices of items to update in the left-hand-side list, got: ${indices} at '${concatStringsSep ''.'' here}'";
assert assertMsg (isList updates) "updating array merge: right-hand-side must be a list, got: ${typeOf updates} at '${concatStringsSep ''.'' here}'";
assert assertMsg (length indices == length updates)
"updating array merge: for each index there must be one corresponding update value, got: ${length indices} indices & ${length updates} updates at '${concatStringsSep ''.'' here}'";
let
updated = listToAttrs (
zipListsWith (idx: upd:
{
name = toString idx;
value = (mergeAt here
{ mergedListItem = (elemAt orig idx); }
{ mergedListItem = upd; }
).mergedListItem;
}
) indices updates);
in imap0 (i: v:
if hasAttr "${toString i}" updated
then updated.${toString i}
else elemAt orig i
) orig;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment