segment util/util.nix into std/*

This commit is contained in:
Emile Clark-Boman 2026-01-25 11:22:55 +10:00
parent e3858bb932
commit 7c1dc8605f
10 changed files with 257 additions and 184 deletions

View file

@ -0,0 +1,17 @@
## Primitive Standard Functions
>[!NOTE]
> This directory is dedicated to porting functions from `nixpkgs.pkgs.lib`.
The `/nt/primitives` directory should have no dependencies on NixTypes *(with one exception explained below)*.
Thus the **NixTypes system must be constructed from a dependency-free standard library.**
This includes dependency on `this` (provided by the `nt.mix` module system)!
`/nt/primitives` is structured as a `nt.mix` module, and hence `/nt/primitives/mix`
must also depend on `/nt/primitives/std`. My point is, **`/nt/primitives/std`
cannot use mix at all!**
### Internal Use Only
**None of these functions are exported for users of NixTypes!** So they should
remain as simple and minimal as possible to avoid extra work maintaining.
Instead, **all of these functions will be reimplemented** post-bootstrap
to be NixType compatible.

View file

@ -0,0 +1,73 @@
{...}: let
inherit
(builtins)
attrNames
elem
elemAt
filter
hasAttr
head
length
mapAttrs
partition
removeAttrs
tail
typeOf
;
in rec {
enfIsAttrs = value: msg: let
got = typeOf value;
in
got == "set" || throw "${msg}: expected primitive nix type \"set\" but got \"${got}\"";
# NOTE: doesn't check if xs is type set, use enfHasAttr instead
enfHasAttrUnsafe = name: xs: msg:
hasAttr name xs || throw "${msg}: missing required attribute \"${name}\"";
# NOTE: use enfHasAttr' if you can guarantee xs is type set
enfHasAttr = name: xs: msg:
enfIsAttrs xs msg && enfHasAttrUnsafe name xs msg;
mergeAttrsList = list: let
# `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end`
# Type: Int -> Int -> Attrs
binaryMerge = start: end:
# assert start < end; # Invariant
if end - start >= 2
then
# If there's at least 2 elements, split the range in two, recurse on each part and merge the result
# The invariant is satisfied because each half will have at least 1 element
binaryMerge start (start + (end - start) / 2) // binaryMerge (start + (end - start) / 2) end
else
# Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly
elemAt list start;
in
if list == []
then
# Calling binaryMerge as below would not satisfy its invariant
{}
else binaryMerge 0 (length list);
removeAttrsRec = paths: xs: let
parts = partition (p: length p == 1) paths;
here = parts.right;
next = parts.wrong;
in
xs
|> flipCurry removeAttrs here
|> mapAttrs (name:
if ! elem name next
then id
else
next
|> filter (x: head x == name)
|> map tail
|> removeAttrsRec);
filterAttrs = pred: xs:
attrNames xs
|> filter (name: ! pred name xs.${name})
|> removeAttrs xs;
nameValuePair = name: value: {inherit name value;};
}

View file

@ -0,0 +1,28 @@
# {mix, ...} @ inputs:
# mix.newMixture inputs (mixture: {
# includes.public = [
# ./attrs.nix
# ./fn.nix
# ./list.nix
# ./num.nix
# ./string.nix
# ];
# })
# WARNING: /nt/primitives/std cannot depend on mix
# WARNING: this file is strictly for bootstrapping nt
let
# input = {inherit this;};
this = {
};
# this =
# import ./util.nix input
# // import ./parse.nix input
# // import ./trapdoor.nix input
# // import ./null.nix input
# // import ./maybe.nix input
# // import ./wrap.nix input
# // import ./enforce.nix input
# // import ./sig.nix input
# // import ./nt.nix input;
in
this

View file

@ -0,0 +1,2 @@
{...}: {
}

10
nt/primitives/std/fn.nix Normal file
View file

@ -0,0 +1,10 @@
{...}: {
id = x: x;
flipCurry = f: a: b: f b a;
# not sure where else to put this...
nullOr = f: x:
if x != null
then f x
else x;
}

View file

@ -0,0 +1,71 @@
{...}: let
inherit
(builtins)
elemAt
foldl'
genList
length
;
in rec {
sublist = start: count: list: let
len = length list;
in
genList (n: elemAt list (n + start)) (
if start >= len
then 0
else if start + count > len
then len - start
else count
);
take = count: sublist 0 count;
init = list:
assert (list != []) || throw "lists.init: list must not be empty!";
take (length list - 1) list;
last = list:
assert (list != []) || throw "lists.last: list must not be empty!";
elemAt list (length list - 1);
# REF: pkgs.lib.lists.reverseList
reverse = xs: let
l = length xs;
in
genList (n: elemAt xs (l - n - 1)) l;
# REF: pkgs.lib.lists.foldr
foldr = op: nul: list: let
len = length list;
fold' = n:
if n == len
then nul
else op (elemAt list n) (fold' (n + 1));
in
fold' 0;
# REF: pkgs.lib.lists.findFirstIndex [MODIFIED]
firstIndexOf = x: list: let
resultIndex =
foldl' (
index: el:
if index < 0
then
# No match yet before the current index, we need to check the element
if el == x
then
# We have a match! Turn it into the actual index to prevent future iterations from modifying it
-index - 1
else
# Still no match, update the index to the next element (we're counting down, so minus one)
index - 1
else
# There's already a match, propagate the index without evaluating anything
index
) (-1)
list;
in
if resultIndex < 0
then null
else resultIndex;
}

30
nt/primitives/std/num.nix Normal file
View file

@ -0,0 +1,30 @@
{...}: let
inherit
(builtins)
elemAt
genList
length
;
in rec {
inc = x: x + 1;
dec = x: x - 1;
countEvensLeq = n: n / 2;
countOddsLeq = n: (n + 1) / 2;
nats = genList (x: x);
odds = genList (x: 2 * x + 1);
oddsLeq = n: countOddsLeq n |> genList (x: 2 * x + 1);
evens = genList (x: 2 * x);
evensLeq = n: countEvensLeq n |> genList (x: 2 * x);
# WARNING: mapOdd/mapEven assuming the indexing set begins even (ie start counting from 0)
mapOdd = f: list: oddsLeq (length list - 1) |> map (i: f (elemAt list i));
mapEven = f: list: evensLeq (length list + 1) |> map (i: f (elemAt list i));
# WARNING: filterOdd/filterEven assuming the indexing set begins even (ie start counting from 0)
filterOdd = mapOdd (x: x);
filterEven = mapEven (x: x);
}

View file

@ -0,0 +1,25 @@
{...}: let
inherit
(builtins)
genList
match
replaceStrings
stringLength
substring
;
in rec {
stringElem = i: substring i 1;
stringTake = substring 0;
stringHead = stringTake 1;
stringTail = x: x |> substring 1 (stringLength x - 1);
stringInit = x: x |> stringTake 1;
stringLast = x: stringElem (stringLength x - 1);
stringToCharacters = s: genList (p: substring p 1 s) (stringLength s);
escape = list: replaceStrings list (map (c: "\\${c}") list);
escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
hasInfix = infix: content:
match ".*${escapeRegex infix}.*" "${content}" != null;
}

View file

@ -1,7 +1,6 @@
{this, ...}: let
inherit
(builtins)
hasAttr
typeOf
;
@ -13,20 +12,12 @@
isTypeSig
toTypeSig
;
in rec {
in {
enfIsType = type: value: msg: let
got = typeOf value;
in
got == type || throw "${msg}: expected primitive nix type \"${type}\" but got \"${got}\"";
# NOTE: doesn't check if xs is type set, use enfHasAttr instead
enfHasAttr' = name: xs: msg:
hasAttr name xs || throw "${msg}: missing required attribute \"${name}\"";
# NOTE: use enfHasAttr' if you can guarantee xs is type set
enfHasAttr = name: xs: msg:
enfIsType "set" xs msg && enfHasAttr' name xs msg;
enfIsClassSig = sig: msg:
isClassSig sig || throw "${msg}: given value \"${toString sig}\" of primitive nix type \"${typeOf sig}\" is not a valid Typeclass signature";
@ -36,8 +27,6 @@ in rec {
enfIsNT = T: msg:
isNT T || throw "${msg}: expected nt compatible type but got \"${toString T}\" of primitive nix type \"${typeOf T}\"";
# assert enfImpls "nt::&Maybe" T "nt::&Maybe.unwrap";
# impls = type: T: assert enfIsNT T "nt.impls"; impls' type T;
enfImpls = type: T: msg:
impls type T || throw "${msg}: given type \"${toTypeSig T}\" does not implement typeclass \"${toTypeSig type}\"";
}

View file

@ -1,172 +0,0 @@
# TODO: move these declarations to a separate module (maybe?)
{...}: let
inherit
(builtins)
attrNames
elem
elemAt
filter
foldl'
head
genList
length
mapAttrs
match
partition
removeAttrs
replaceStrings
stringLength
substring
tail
;
in rec {
id = x: x;
flipCurry = f: a: b: f b a;
inc = x: x + 1;
dec = x: x - 1;
sublist = start: count: list: let
len = length list;
in
genList (n: elemAt list (n + start)) (
if start >= len
then 0
else if start + count > len
then len - start
else count
);
take = count: sublist 0 count;
init = list:
assert (list != []) || throw "lists.init: list must not be empty!";
take (length list - 1) list;
last = list:
assert (list != []) || throw "lists.last: list must not be empty!";
elemAt list (length list - 1);
# REF: pkgs.lib.lists.reverseList
reverse = xs: let
l = length xs;
in
genList (n: elemAt xs (l - n - 1)) l;
# REF: pkgs.lib.lists.foldr
foldr = op: nul: list: let
len = length list;
fold' = n:
if n == len
then nul
else op (elemAt list n) (fold' (n + 1));
in
fold' 0;
# REF: pkgs.lib.lists.findFirstIndex [MODIFIED]
firstIndexOf = x: list: let
resultIndex =
foldl' (
index: el:
if index < 0
then
# No match yet before the current index, we need to check the element
if el == x
then
# We have a match! Turn it into the actual index to prevent future iterations from modifying it
-index - 1
else
# Still no match, update the index to the next element (we're counting down, so minus one)
index - 1
else
# There's already a match, propagate the index without evaluating anything
index
) (-1)
list;
in
if resultIndex < 0
then null
else resultIndex;
nullOr = f: x:
if x != null
then f x
else x;
stringElem = i: substring i 1;
stringTake = substring 0;
stringHead = stringTake 1;
stringTail = x: x |> substring 1 (stringLength x - 1);
stringInit = x: x |> stringTake 1;
stringLast = x: stringElem (stringLength x - 1);
stringToCharacters = s: genList (p: substring p 1 s) (stringLength s);
escape = list: replaceStrings list (map (c: "\\${c}") list);
escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
hasInfix = infix: content:
match ".*${escapeRegex infix}.*" "${content}" != null;
countEvensLeq = n: n / 2;
countOddsLeq = n: (n + 1) / 2;
nats = genList id;
odds = genList (x: 2 * x + 1);
oddsLeq = n: countOddsLeq n |> genList (x: 2 * x + 1);
evens = genList (x: 2 * x);
evensLeq = n: countEvensLeq n |> genList (x: 2 * x);
# WARNING: mapOdd/mapEven assuming the indexing set begins even (ie start counting from 0)
mapOdd = f: list: oddsLeq (length list - 1) |> map (i: f (elemAt list i));
mapEven = f: list: evensLeq (length list + 1) |> map (i: f (elemAt list i));
# WARNING: filterOdd/filterEven assuming the indexing set begins even (ie start counting from 0)
filterOdd = mapOdd id;
filterEven = mapEven id;
mergeAttrsList = list: let
# `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end`
# Type: Int -> Int -> Attrs
binaryMerge = start: end:
# assert start < end; # Invariant
if end - start >= 2
then
# If there's at least 2 elements, split the range in two, recurse on each part and merge the result
# The invariant is satisfied because each half will have at least 1 element
binaryMerge start (start + (end - start) / 2) // binaryMerge (start + (end - start) / 2) end
else
# Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly
elemAt list start;
in
if list == []
then
# Calling binaryMerge as below would not satisfy its invariant
{}
else binaryMerge 0 (length list);
removeAttrsRec = paths: xs: let
parts = partition (p: length p == 1) paths;
here = parts.right;
next = parts.wrong;
in
xs
|> flipCurry removeAttrs here
|> mapAttrs (name:
if ! elem name next
then id
else
next
|> filter (x: head x == name)
|> map tail
|> removeAttrsRec);
filterAttrs = pred: xs:
attrNames xs
|> filter (name: ! pred name xs.${name})
|> removeAttrs xs;
nameValuePair = name: value: {inherit name value;};
}