2026-01-28 17:27:48 +10:00
|
|
|
|
# ❄ NixTypes (nt) ❄
|
2026-01-28 18:42:33 +10:00
|
|
|
|
<p align="center">Because Nix never held your hand. It shot off your fingers and spat out God's longest stack trace</p>
|
2025-12-14 14:35:15 +10:00
|
|
|
|
|
2026-01-28 17:27:48 +10:00
|
|
|
|
>[!WARNING]
|
|
|
|
|
|
> ✨ **Under Construction** ✨
|
2026-02-09 09:36:50 +10:00
|
|
|
|
>
|
2026-01-28 18:04:58 +10:00
|
|
|
|
> NixTypes is **quite** a large project to do alone, but it's been staring at me for the last 12 months.
|
2026-01-28 17:27:48 +10:00
|
|
|
|
> If you're interested feel free to contact me and/or submit pull requests :yellow_heart::yellow_heart:
|
2026-02-09 09:36:50 +10:00
|
|
|
|
>
|
2026-01-28 17:27:48 +10:00
|
|
|
|
> **Be not afraid!** It's only a matter of time until NixTypes is ready for use!
|
2025-12-14 14:35:15 +10:00
|
|
|
|
|
2026-02-09 09:36:50 +10:00
|
|
|
|
>[!CAUTION]
|
|
|
|
|
|
> The current syntax is quite ugly and **no it's not final.** NixTypes is in an
|
|
|
|
|
|
> **extremely experimental** prototyping stage!
|
|
|
|
|
|
|
2026-01-28 17:27:48 +10:00
|
|
|
|
## 💙 Huh?
|
|
|
|
|
|
Nix has no type system duh!? Sure that's fine for configuring your distro,
|
|
|
|
|
|
but what about developing in Nix itself? The code people write tends to be unnecessarily complex
|
|
|
|
|
|
or full or bugs. Nix needs standards, and NixTypes gives you those.
|
|
|
|
|
|
|
|
|
|
|
|
## :rainbow: More Than Types
|
|
|
|
|
|
NixTypes isn't *exactly* just a type system. **You didn't think I'd give you types
|
|
|
|
|
|
then say goodbye did you?** Then we'd be right back where we started... Instead
|
|
|
|
|
|
there's a whole standard library built from the ashes.
|
|
|
|
|
|
|
|
|
|
|
|
Some of the sweet sweet batteries included:
|
|
|
|
|
|
1. **Pattern Matching** (finally!!)
|
|
|
|
|
|
2. **Attribute Set Parsing**
|
|
|
|
|
|
3. **Pretty Printing** (no more `builtins.toString` errors)
|
|
|
|
|
|
4. **A Module System** (say goodbye to managing all your `imports`)
|
|
|
|
|
|
5. **Types, Types, & More Types** (Maybe/Some/None, Monads, Tree, Rose, etc)
|
2026-01-28 23:54:53 +10:00
|
|
|
|
6. **Support For Pipe Operators** (cleaner code with `<|` and `|>` )
|
2026-01-28 18:04:58 +10:00
|
|
|
|
|
2026-01-28 18:42:33 +10:00
|
|
|
|
## Types For Humans
|
2026-01-29 00:11:33 +10:00
|
|
|
|
Let's design a `Result` type using the NixTypes system!
|
|
|
|
|
|
We'll start simple and use the following attribute set
|
|
|
|
|
|
as our naive basis:
|
|
|
|
|
|
```nix
|
|
|
|
|
|
Result = success: value:
|
|
|
|
|
|
assert builtins.isBool success;
|
|
|
|
|
|
{ inherit success value; };
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
We can
|
|
|
|
|
|
```nix
|
|
|
|
|
|
let
|
|
|
|
|
|
inherit (nt)
|
|
|
|
|
|
print
|
|
|
|
|
|
toString
|
|
|
|
|
|
Bool
|
|
|
|
|
|
Fn
|
|
|
|
|
|
Type
|
|
|
|
|
|
;
|
|
|
|
|
|
in rec {
|
|
|
|
|
|
Result = Type (Self: {
|
|
|
|
|
|
ops = {
|
|
|
|
|
|
# we need to make a constructor
|
|
|
|
|
|
mk = Fn [Bool Any] Self (success: value: { inherit success value; });
|
|
|
|
|
|
# create some alternative constructors
|
|
|
|
|
|
mkSuccess = Self.mk true;
|
|
|
|
|
|
mkFail = Self.mk false;
|
|
|
|
|
|
|
|
|
|
|
|
isSuccess = self: self.success;
|
|
|
|
|
|
isFail = self: ! self.success;
|
|
|
|
|
|
|
|
|
|
|
|
unwrap = self: self.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
# Example Usage:
|
|
|
|
|
|
tryGetAttr = name: attrs: let
|
|
|
|
|
|
success = attrs ? name;
|
|
|
|
|
|
value = attrs.${name} or "AttrSet missing attribute name \"${name}\"";
|
|
|
|
|
|
in
|
|
|
|
|
|
Result success value;
|
|
|
|
|
|
|
|
|
|
|
|
# prints myAttr and returns the default value (same as builtins.trace!)
|
|
|
|
|
|
printMyAttr = attrs: default: let
|
|
|
|
|
|
result = tryGetAttr "myAttr";
|
|
|
|
|
|
in
|
|
|
|
|
|
if result.isSuccess
|
|
|
|
|
|
then print (toString result.unwrap) default
|
|
|
|
|
|
else default;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Now let's try something a little harder and try to make our
|
|
|
|
|
|
`Result` type act more like Rust's `std::result` crate:
|
|
|
|
|
|
```rust
|
|
|
|
|
|
enum Result<T, E> {
|
|
|
|
|
|
Ok(T),
|
|
|
|
|
|
Err(E),
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
>[!TODO]
|
|
|
|
|
|
> I'm sleepy and I'll finish this in the morning...
|
2026-01-28 18:42:33 +10:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Types (Not) For Humans
|
|
|
|
|
|
Let's define a Maybe type in two diferent ways:
|
|
|
|
|
|
1. As a polymorphic type `PolyMaybe := Some | None` for monads `Some` and `None`
|
|
|
|
|
|
2. And as an lax-idempotent (Kock–Zöberlein) monad `KZMaybe` with states `KZSome` and `KZNone`
|
|
|
|
|
|
```nix
|
|
|
|
|
|
let
|
|
|
|
|
|
inherit
|
|
|
|
|
|
(nt)
|
|
|
|
|
|
mk
|
|
|
|
|
|
enfNotNull
|
|
|
|
|
|
Type
|
|
|
|
|
|
Sum
|
|
|
|
|
|
Monad'
|
|
|
|
|
|
KZMonad' # apostrophe implies typeclass
|
|
|
|
|
|
;
|
|
|
|
|
|
in rec {
|
|
|
|
|
|
# === METHOD 1:
|
|
|
|
|
|
Some = Type {
|
|
|
|
|
|
ops = {
|
|
|
|
|
|
Monad'.result = value: mk { inherit value; };
|
|
|
|
|
|
Monad'.unwrap = self: self.value;
|
|
|
|
|
|
Monad'.bind = self: f: f self.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
impls = [ Monad' ];
|
|
|
|
|
|
};
|
|
|
|
|
|
None = Type {
|
|
|
|
|
|
ops.mk = mk {
|
|
|
|
|
|
Monad'.result = mk {};
|
|
|
|
|
|
Monad'.unwrap = _: null;
|
|
|
|
|
|
Monad'.bind = _: _: null;
|
|
|
|
|
|
};
|
|
|
|
|
|
impls = [ Monad' ];
|
|
|
|
|
|
};
|
|
|
|
|
|
PolyMaybe = Sum [Some None];
|
|
|
|
|
|
|
|
|
|
|
|
# === METHOD 2:
|
|
|
|
|
|
KZSome = value:
|
|
|
|
|
|
assert enfNotNull value "KZSome value";
|
|
|
|
|
|
KZMaybe;
|
|
|
|
|
|
KZNone = KZMaybe null;
|
|
|
|
|
|
KZMaybe = Type {
|
|
|
|
|
|
ops = {
|
|
|
|
|
|
# Lift a value INTO the monadic context
|
|
|
|
|
|
# NOTE: because KZMaybe implements KZMonad' lax-idempotence
|
|
|
|
|
|
# NOTE: is automatically added to the result operation
|
|
|
|
|
|
Monad'.result = value: mk { inherit value; };
|
|
|
|
|
|
# Lift a value OUT OF the monadic context
|
|
|
|
|
|
Monad'.unwrap = self: self.value;
|
|
|
|
|
|
# Alter a value inside the monadic context
|
|
|
|
|
|
Monad'.bind = f: x: f x;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
impls = [ KZMonad' ];
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-28 18:04:58 +10:00
|
|
|
|
|
|
|
|
|
|
### ❄🎁 Parse the Parcel
|
|
|
|
|
|
*Close your eyes with me ok? MMmmmmmmmmm yes just like that...* **NOW** imagine
|
|
|
|
|
|
you're a moderately depressed and very sleepy programmer. You're reading a README.md
|
|
|
|
|
|
for a type system in Nix, I doubt it's that hard. Then **whoooshhh boooom**, Zeus
|
|
|
|
|
|
himself hath commended thee to fulfill his greatest ambition **OR DIE!!** You must
|
|
|
|
|
|
write a function taking an attribute set of the structure
|
|
|
|
|
|
```nix
|
|
|
|
|
|
{
|
|
|
|
|
|
a = { # optional
|
|
|
|
|
|
b = { # optional
|
|
|
|
|
|
c = ...; # optional (any type, default: null)
|
|
|
|
|
|
d = ...; # optional (string, default: "hola")
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
e = { # required
|
|
|
|
|
|
f = ...; # required (path)
|
|
|
|
|
|
g = ...; # optional (function, default: (x: x))
|
|
|
|
|
|
h = ...; # optional (string, default: "null")
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
Not having a great day now are you? It's doable and not overtly difficult,
|
|
|
|
|
|
but coming back in a couple months you'd have to decipher your solution.
|
|
|
|
|
|
**NOW BEHOLD:**
|
|
|
|
|
|
```nix
|
|
|
|
|
|
# nix made simple <3
|
2026-01-28 23:54:53 +10:00
|
|
|
|
let
|
|
|
|
|
|
inherit
|
|
|
|
|
|
(nt)
|
|
|
|
|
|
projectOnto
|
|
|
|
|
|
missing
|
|
|
|
|
|
verify
|
|
|
|
|
|
;
|
|
|
|
|
|
in
|
|
|
|
|
|
{
|
|
|
|
|
|
f = attrs:
|
|
|
|
|
|
attrs
|
|
|
|
|
|
|> projectOnto
|
|
|
|
|
|
{
|
|
|
|
|
|
a = {
|
|
|
|
|
|
b = {
|
|
|
|
|
|
c = null;
|
|
|
|
|
|
d = "hola";
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
e = {
|
|
|
|
|
|
f = required "error message...";
|
|
|
|
|
|
g = x: x;
|
|
|
|
|
|
h = null |> verify isString;
|
2026-01-28 18:04:58 +10:00
|
|
|
|
};
|
|
|
|
|
|
};
|
2026-01-28 23:54:53 +10:00
|
|
|
|
|> ...; # your logic here...
|
|
|
|
|
|
}
|
2026-01-28 18:04:58 +10:00
|
|
|
|
```
|