The small pure-utility dependency that lets the pure gen substrate drop nixpkgs.lib.
It is builtins re-exports plus the handful of pure utilities (genAttrs, unique,
filterAttrs, fix, optional, toposort, …) the substrate uses, vendored
behavior-identically from nixpkgs lib.
Not a type system, not a module-system shim — only general pure utilities. The
lib.types / mkOption / evalModules tier is a separate concern (a Korora-class
replacement) and out of scope here.
Dependency class: A (pure) — the lib takes no flake inputs and imports nothing from
nixpkgs.lib; it is the nixpkgs-lib-free base every other pure gen lib depends on. The
lib flake carries zero inputs, so a consumer's lock gains no transitive nixpkgs
dependency from pulling gen-prelude in. Purity is enforced structurally (the flake has
no inputs to draw a lib from), and every vendored utility is held byte-behavior
-identical to its nixpkgs counterpart by the prelude-fidelity test suite.
gen-prelude is a single attrset of pure functions — no inputs, no functor. Two kinds of member:
builtinsre-exports — direct aliases of Nixbuiltins(map,filter,foldl',genList,partition, …), grouped under one name so consumers need not reach intobuiltinsthemselves.- Vendored pure utilities — the small set of
nixpkgs.libhelpers the gen substrate actually uses (genAttrs,filterAttrs,unique,fix,toposort, …), copied here so the pure gen libraries can depend on gen-prelude instead ofnixpkgs.lib. Each is behavior-identical to itslib.*original;toposort(with itslistDfs/ list- reverse helpers) is copied verbatim so its{ result } | { cycle; loops }contract matches nixpkgs exactly.
The mental model: wherever a pure gen lib would have written lib.X, it writes
prelude.X instead and gets identical semantics with no nixpkgs.lib in its closure.
Downstream libraries — gen-graph (phaseOrder over condensation), gen-vars, gen-scope,
gen-dispatch, and others — build on exactly this surface.
| Library | Role |
|---|---|
| gen-prelude | This lib — pure nixpkgs-lib-free utility base (builtins re-exports + vendored lib utils) |
| gen-algebra | Pure primitives (record, search monad, either, intensional identity) |
| gen-schema | Typed registries (kinds, instances, collections, refs) |
| gen-aspects | Aspect type system (traits, classification, dispatch) |
| gen-scope | HOAG scope-graph evaluator (demand-driven, _eval memoization, circular attributes) |
| gen-graph | Accessor-based graph query combinators (traversal, condensation, phaseOrder) |
| gen-select | Selector algebra (pattern matching over graph positions) |
| gen-bind | Module binding (inject external args into NixOS modules) |
| gen-dispatch | Relational rule dispatch STEP (stratified phases, conflict resolution) |
| gen-resolve | Demand-driven RAG evaluator over scope graphs (attribute schedule + convergence loop) |
| gen-rebuild | Pure-Nix incremental rebuilder (change propagation, AFFECTED set) |
| gen-vars | Pure-Nix vars/secrets (den-agnostic) |
{
inputs.gen-prelude.url = "github:sini/gen-prelude";
outputs = { gen-prelude, ... }:
let
prelude = gen-prelude.lib;
in
{
example = prelude.genAttrs [ "a" "b" ] (n: n + "!");
# => { a = "a!"; b = "b!"; }
};
}gen-prelude has no inputs, so nothing transitive (no nixpkgs) enters your lock. The
.lib output is a plain value — there is no gen-prelude { inherit lib; } functor call.
let
prelude = import "${builtins.fetchGit { url = "https://github.com/sini/gen-prelude"; }}/lib";
# or, against a local checkout: prelude = import ./path/to/gen-prelude/lib;
in
prelude.unique [ 3 1 1 2 3 ] # => [ 3 1 2 ]import ./lib (equivalently import ./default.nix) evaluates directly to the lib
attrset — no arguments, since the lib depends on nothing.
Every name below is a top-level member of the lib attrset (verified against
nix eval .#lib --apply builtins.attrNames). 45 members total.
Direct aliases of Nix builtins, re-exported so consumers depend only on gen-prelude:
all any attrNames attrValues concatLists concatMap concatStringsSep elem
elemAt filter foldl' functionArgs genList head isAttrs isFunction isList
length listToAttrs map mapAttrs match partition sort stringLength substring
tail
Semantics are exactly those of the corresponding builtins.*.
Behavior-identical copies of nixpkgs.lib helpers:
genAttrs names f— attrset with each name innamesmapped tof name.nameValuePair name value—{ inherit name value; }(pairs forlistToAttrs).optional cond x—[ x ]ifcondelse[ ].optionalAttrs cond attrs—attrsifcondelse{ }.optionalString cond s—sifcondelse"".last xs— final element (throws on[ ]).init xs— all but the final element (throws on[ ]).unique xs— order-preserving deduplication.filterAttrs pred attrs— attrset keeping entries wherepred name value.mapAttrsToList f attrs— list off name valueover the attrset.concatMapStringsSep sep f xs—map f xsjoined bysep.hasPrefix pre s— whethersstarts withpre.imap0 f xs—mapwith a 0-based index:f index element.fix f— least fixed pointlet x = f x; in x.max a b— the larger of two comparables.range from to— inclusive integer range ([ ]whenfrom > to).removePrefix pre s—swith a leadingprestripped (unchanged if absent).toposort before list— partial-order topological sort. Returns{ result = sorted; }or, on a cycle,{ cycle; loops; }— identical tonixpkgs lib.toposort. Copied verbatim (with its internallistDfs/ list-reverse helpers) so the contract matches nixpkgs exactly; consumers such as gen-graph'sphaseOrderand gen-vars rely on the? resultdiscriminant.
cd ci && nix flake checkThe ci/ directory is a separate flake (it pulls nixpkgs only to supply the lib
oracle the fidelity suite compares against — the lib itself pulls nothing). It runs
41 tests across 2 suites:
prelude(7) — readable literal-expectation sanity checks (genAttrs,unique,filterAttrs,fix,toposortresult + cycle, empty-list throw).prelude-fidelity(34) — the load-bearing guard: for every vendored utility,prelude.X input == lib.X inputover normal and boundary inputs (empty lists, absent prefixes, reversed ranges, cycles). This is what keeps the vendored copies byte-behavior-identical to nixpkgslib.
There is no separate purity suite because purity is structural: the lib flake declares
no inputs, so there is no nixpkgs.lib in scope to accidentally depend on.
gen-prelude has no research lineage — it is plumbing. The builtins members are direct
re-exports of the Nix builtins set. The vendored utilities are copied
behavior-identically from nixpkgs lib:
| Utility | nixpkgs source |
|---|---|
genAttrs, filterAttrs, mapAttrsToList, nameValuePair, optionalAttrs |
lib/attrsets.nix |
optional, last, init, unique, imap0, range, toposort (+ listDfs) |
lib/lists.nix |
optionalString, concatMapStringsSep, hasPrefix, removePrefix |
lib/strings.nix |
fix, max |
lib/trivial.nix / lib/fixed-points.nix |
toposort is copied verbatim so its { result } | { cycle; loops } contract matches
nixpkgs exactly. The prelude-fidelity test suite asserts each utility stays
behavior-identical to its nixpkgs.lib original, so the vendoring cannot silently
drift.
MIT — see LICENSE.