Skip to content

Commit

Permalink
lib: improve DAG library
Browse files Browse the repository at this point in the history
Specifically,

- directly export `modules/lib/dag.nix` instead of renaming
  attributes,

- run through utilities to reuse code where possible,

- expose `lib.hm.dag.isEntry` and reuse it in
  `modules/lib/types-dag.nix`,

- reuse utilities through `lib` set instead of passing imports to
  functions, and

- eta reduction of `map`, `entryAnywhere`, `entryAfter` and
  `entryBefore`.
  • Loading branch information
rcerc authored and rycee committed Jul 18, 2022
1 parent 30dda62 commit 4a724cb
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 79 deletions.
95 changes: 41 additions & 54 deletions modules/lib/dag.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,38 @@
#
# - not specific to strings, i.e., any payload is OK,
#
# - the addition of the function `dagEntryBefore` indicating a
# "wanted by" relationship.
# - the addition of the function `entryBefore` indicating a "wanted
# by" relationship.

{ lib }:

let inherit (lib) all any filterAttrs mapAttrs mapAttrsToList toposort;
in rec {

emptyDag = { };
let inherit (lib) all filterAttrs hm mapAttrs toposort;
in {
empty = { };

isEntry = e: e ? data && e ? after && e ? before;
isDag = dag:
let isEntry = e: (e ? data) && (e ? after) && (e ? before);
in builtins.isAttrs dag && all (x: x) (mapAttrsToList (n: isEntry) dag);
builtins.isAttrs dag && all hm.dag.isEntry (builtins.attrValues dag);

# Takes an attribute set containing entries built by
# dagEntryAnywhere, dagEntryAfter, and dagEntryBefore to a
# topologically sorted list of entries.
# Takes an attribute set containing entries built by entryAnywhere,
# entryAfter, and entryBefore to a topologically sorted list of
# entries.
#
# Internally this function uses the `toposort` function in
# `<nixpkgs/lib/lists.nix>` and its value is accordingly.
#
# Specifically, the result on success is
#
# { result = [{name = ?; data = ?;} …] }
# { result = [ { name = ?; data = ?; } … ] }
#
# For example
#
# nix-repl> dagTopoSort {
# a = dagEntryAnywhere "1";
# b = dagEntryAfter ["a" "c"] "2";
# c = dagEntryBefore ["d"] "3";
# d = dagEntryBefore ["e"] "4";
# e = dagEntryAnywhere "5";
# nix-repl> topoSort {
# a = entryAnywhere "1";
# b = entryAfter [ "a" "c" ] "2";
# c = entryBefore [ "d" ] "3";
# d = entryBefore [ "e" ] "4";
# e = entryAnywhere "5";
# } == {
# result = [
# { data = "1"; name = "a"; }
Expand All @@ -51,66 +50,54 @@ in rec {
# And the result on error is
#
# {
# cycle = [ {after = ?; name = ?; data = ?} … ];
# loops = [ {after = ?; name = ?; data = ?} … ];
# cycle = [ { after = ?; name = ?; data = ? } … ];
# loops = [ { after = ?; name = ?; data = ? } … ];
# }
#
# For example
#
# nix-repl> dagTopoSort {
# a = dagEntryAnywhere "1";
# b = dagEntryAfter ["a" "c"] "2";
# c = dagEntryAfter ["d"] "3";
# d = dagEntryAfter ["b"] "4";
# e = dagEntryAnywhere "5";
# nix-repl> topoSort {
# a = entryAnywhere "1";
# b = entryAfter [ "a" "c" ] "2";
# c = entryAfter [ "d" ] "3";
# d = entryAfter [ "b" ] "4";
# e = entryAnywhere "5";
# } == {
# cycle = [
# { after = ["a" "c"]; data = "2"; name = "b"; }
# { after = ["d"]; data = "3"; name = "c"; }
# { after = ["b"]; data = "4"; name = "d"; }
# { after = [ "a" "c" ]; data = "2"; name = "b"; }
# { after = [ "d" ]; data = "3"; name = "c"; }
# { after = [ "b" ]; data = "4"; name = "d"; }
# ];
# loops = [
# { after = ["a" "c"]; data = "2"; name = "b"; }
# { after = [ "a" "c" ]; data = "2"; name = "b"; }
# ];
# } == {}
# }
# true
dagTopoSort = dag:
topoSort = dag:
let
dagBefore = dag: name:
mapAttrsToList (n: v: n)
(filterAttrs (n: v: any (a: a == name) v.before) dag);
builtins.attrNames
(filterAttrs (n: v: builtins.elem name v.before) dag);
normalizedDag = mapAttrs (n: v: {
name = n;
data = v.data;
after = v.after ++ dagBefore dag n;
}) dag;
before = a: b: any (c: a.name == c) b.after;
sorted = toposort before (mapAttrsToList (n: v: v) normalizedDag);
before = a: b: builtins.elem a.name b.after;
sorted = toposort before (builtins.attrValues normalizedDag);
in if sorted ? result then {
result = map (v: { inherit (v) name data; }) sorted.result;
} else
sorted;

# Applies a function to each element of the given DAG.
dagMap = f: dag: mapAttrs (n: v: v // { data = f n v.data; }) dag;

# Create a DAG entry with no particular dependency information.
dagEntryAnywhere = data: {
inherit data;
before = [ ];
after = [ ];
};

dagEntryBetween = before: after: data: { inherit data before after; };
map = f: mapAttrs (n: v: v // { data = f n v.data; });

dagEntryAfter = after: data: {
inherit data after;
before = [ ];
};
entryBetween = before: after: data: { inherit data before after; };

dagEntryBefore = before: data: {
inherit data before;
after = [ ];
};
# Create a DAG entry with no particular dependency information.
entryAnywhere = hm.dag.entryBetween [ ] [ ];

entryAfter = hm.dag.entryBetween [ ];
entryBefore = before: hm.dag.entryBetween before [ ];
}
17 changes: 2 additions & 15 deletions modules/lib/default.nix
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
{ lib }:

rec {
dag =
let
d = import ./dag.nix { inherit lib; };
in
{
empty = d.emptyDag;
isDag = d.isDag;
topoSort = d.dagTopoSort;
map = d.dagMap;
entryAnywhere = d.dagEntryAnywhere;
entryBetween = d.dagEntryBetween;
entryAfter = d.dagEntryAfter;
entryBefore = d.dagEntryBefore;
};
dag = import ./dag.nix { inherit lib; };

assertions = import ./assertions.nix { inherit lib; };

booleans = import ./booleans.nix { inherit lib; };
gvariant = import ./gvariant.nix { inherit lib; };
maintainers = import ./maintainers.nix;
strings = import ./strings.nix { inherit lib; };
types = import ./types.nix { inherit dag gvariant lib; };
types = import ./types.nix { inherit gvariant lib; };

shell = import ./shell.nix { inherit lib; };
zsh = import ./zsh.nix { inherit lib; };
Expand Down
12 changes: 5 additions & 7 deletions modules/lib/types-dag.nix
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
{ dag, lib }:
{ lib }:

let
inherit (lib)
concatStringsSep defaultFunctor fixedWidthNumber imap1 isAttrs isList length
listToAttrs mapAttrs mkIf mkOrder mkOption mkOptionType nameValuePair
concatStringsSep defaultFunctor fixedWidthNumber hm imap1 isAttrs isList
length listToAttrs mapAttrs mkIf mkOrder mkOption mkOptionType nameValuePair
stringLength types warn;

isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);

dagEntryOf = elemType:
let
submoduleType = types.submodule ({ name, ... }: {
Expand All @@ -21,10 +19,10 @@ let
};
});
maybeConvert = def:
if isDagEntry def.value then
if hm.dag.isEntry def.value then
def.value
else
dag.entryAnywhere (if def ? priority then
hm.dag.entryAnywhere (if def ? priority then
mkOrder def.priority def.value
else
def.value);
Expand Down
5 changes: 2 additions & 3 deletions modules/lib/types.nix
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{ lib, dag ? import ./dag.nix { inherit lib; }
, gvariant ? import ./gvariant.nix { inherit lib; } }:
{ lib, gvariant ? import ./gvariant.nix { inherit lib; } }:

let
inherit (lib)
all concatMap foldl' getFiles getValues head isFunction literalExpression
mergeAttrs mergeDefaultOption mergeOneOption mergeOptions mkOption
mkOptionType showFiles showOption types;

typesDag = import ./types-dag.nix { inherit dag lib; };
typesDag = import ./types-dag.nix { inherit lib; };

# Needed since the type is called gvariant and its merge attribute
# must refer back to the type.
Expand Down

0 comments on commit 4a724cb

Please sign in to comment.