Getting Started
Installation
Add nix-effects as a flake input:
{
inputs.nix-effects.url = "github:kleisli-io/nix-effects";
outputs = { nix-effects, nixpkgs, ... }:
let
fx = nix-effects.lib;
in {
# fx.types, fx.run, fx.send, fx.bind, fx.effects, fx.stream ...
};
}
Or import directly without flakes:
let fx = import ./path/to/nix-effects { lib = nixpkgs.lib; };
in ...
Your first type
Define a type with fx.types.refined:
let
inherit (fx.types) String refined;
TargetClass = refined "TargetClass" String
(x: builtins.elem x [ "module" "file" "package" "check" ]);
in {
# Kernel decision procedure — fast boolean check
ok = TargetClass.check "module"; # true
bad = TargetClass.check "fleet"; # false
# Effectful validate — runs through the trampoline, produces blame context
result = fx.run (TargetClass.validate "fleet")
fx.effects.typecheck.collecting [];
# result.state = [ { context = "TargetClass"; ... } ]
}
Your first dependent type
One field's type depends on another field's value — this is a genuine dependent type, checked by the MLTT proof-checking kernel:
let
inherit (fx.types) Bool String ListOf DepRecord refined;
TargetClass = refined "TargetClass" String
(x: builtins.elem x [ "module" "file" "package" "check" ]);
AspectDecl = DepRecord [
{ name = "generated"; type = Bool; }
{ name = "target"; type = self:
if self.generated then TargetClass else String; }
{ name = "requires"; type = _: ListOf String; }
];
in {
ok = AspectDecl.checkFlat {
generated = true;
target = "module";
requires = [ "toolchain" ];
}; # true
bad = AspectDecl.checkFlat {
generated = true;
target = "fleet";
requires = [ ];
}; # false
}
Your first effect
Write a computation, then choose the handler:
let
inherit (fx) pure bind run;
inherit (fx.effects) get put;
# Double the state
doubleState = bind get (s: bind (put (s * 2)) (_: pure s));
result = run doubleState fx.effects.state.handler 21;
# result.value = 21 (old state returned)
# result.state = 42 (state doubled)
in result
A first generated datatype
Generated datatypes are the next step after validation. They give a DSL its constructors, its type, and reusable structural metadata:
let
H = fx.types.hoas;
Aspect = H.datatype "Aspect" [
(H.con "aspect" [
(H.field "name" H.string)
(H.field "target" H.string)
(H.field "requires" (H.listOf H.string))
])
];
in Aspect
The generated Aspect shape can be checked, viewed, reviewed,
documented, traversed for dependencies, interpreted by handlers, and
ornamented with derived fields.
Running checks
The repo includes runnable tests and examples:
git clone https://github.com/kleisli-io/nix-effects
cd nix-effects
# Run all tests
nix flake check
The kernel behind every type
Every .check call runs the MLTT type-checking kernel. When you write
TargetClass.check "module", the kernel elaborates "module" into a
term, type-checks it, and returns a boolean. This is not a separate
system — it is what .check does.
You can also write verified implementations using HOAS combinators:
let
H = fx.types.hoas;
v = fx.types.verified;
# Write a function in HOAS, kernel type-checks it, extract as Nix function
succ = v.verify (H.forall "x" H.nat (_: H.nat))
(v.fn "x" H.nat (x: H.succ x));
in
succ 5 # 6 — a certified Nix function
The kernel checks the implementation against its type at nix eval time.
If the types don't match, you get an error before anything builds.
See the Kernel Architecture
chapter for the full pipeline.
What's in the box
The fx attrset is the entire public API:
| Namespace | Contents |
|---|---|
fx.pure, fx.bind, fx.send, fx.map, fx.seq | Freer monad kernel |
fx.run, fx.handle | Trampoline interpreter |
fx.adapt, fx.adaptHandlers | Handler composition |
fx.types.* | Type system (primitives, constructors, dependent, refinement, universe) |
fx.types.hoas | HOAS surface combinators for the kernel |
fx.types.elaborateType, etc. | Elaboration bridge: fx.types ↔ kernel |
fx.types.verified | Convenience combinators for writing verified implementations |
fx.effects.* | Built-in effects (state, error, reader, writer, acc, choice, conditions, typecheck, linear) |
fx.stream.* | Effectful lazy sequences |