Skip to content

Dnzl

Actor system for Nix. Behaviours are functions. Built on stream-based cycles from Ned.
# A behaviour is a plain Nix function
counter = count: msg:
if msg == "inc" then reply.right (count + 1) // become (counter (count + 1))
else if msg == "get" then reply.right count
else { }; # unknown message → no reply, no state change
counter-c = actor (counter 0);
result = counter-c { inbox = st "inc" "inc" "get"; };
result.outbox.toList
# → [ { right = 1; } { right = 2; } { right = 2; } ]

Wire actors by passing outbox as inbox. Nix laziness resolves evaluation order.

# Scatter: validate N inputs in parallel; gather: merge results
inputs = [ 5 (-1) 8 0 2 ];
actors = builtins.map (n: validator-c { inbox = st n; }) inputs;
results = merge (builtins.map (a: a.outbox) actors);
results.toList
# → [ { right = 5; } { left = "non-positive: -1"; } { right = 8; }
# { left = "non-positive: 0"; } { right = 2; } ]
# Ping-pong via lazy let — pong feeds ping
pong = counter-c { inherit inbox; };
ping = counter-c { inbox = pong.outbox (st.map (_: "get")); };
# Either split: route successes and errors to separate streams
validated = validator-c { inbox = st 5 (-1) 8 0 2; };
validated.outbox.right.toList # → [ 5 8 2 ]
validated.outbox.left.toList # → [ "non-positive: -1" "non-positive: 0" ]

A cycle-c is a Nix value. Return it as a reply — the receiver gains the ability to invoke that actor.

# Server vends fresh counter sessions on connect
session-server-c = actor (_: reply counter-c);
session-refs = (session-server-c { inbox = st "connect" "connect"; }).outbox;
# Each ref is an independent session
sessions = session-refs.flatMap (ref: (ref { inbox = st "inc" "inc" "get"; }).outbox);
sessions.toList
# → [ { right = 1; } { right = 2; } { right = 2; } ← session 1
# { right = 1; } { right = 2; } { right = 2; } ] ← session 2

Tag replies with client id. Each client filters the shared outbox.

server-c = actor (msg: reply { id = msg.id; result = msg.value * 2; });
server = server-c { inbox = shared-bus; };
client-a = server.outbox.filter (r: r.id == "a");
client-b = server.outbox.filter (r: r.id == "b");

Pure Nix

Actors are functions. State is a closure. Pipelines are let bindings.

Lazy pipelines

Nix laziness resolves actor dependency order. Ping-pong, feedback loops, N-stage pipelines — all let bindings.

Either routing

reply.right / reply.left tag outbox entries. outbox.right and outbox.left split the stream without filtering.

Refs as capabilities

A cycle-c is a value. Return it as a reply. Holding the ref is the permission.

Contribute Community Sponsor