Reply and Become
reply constructs the reply field in a behaviour’s return attrset:
reply 42 # { reply = 42; }reply.right 42 # { reply = { right = 42; }; }reply.left "err" # { reply = { left = "err"; }; }reply is an attrset with __functor, so it works both as a function and as a namespace. You can also write { reply = value; } directly — reply is sugar.
reply data # plain value in outboxreply.right data # tagged successreply.left data # tagged failurebecome
Section titled “become”become returns { next-behaviour = new-beh }, telling the actor to use new-beh for the next message:
become new-beh # { next-behaviour = new-beh; }Combine with reply using //:
counter = count: msg: if msg == "inc" then reply.right (count + 1) // become (counter (count + 1)) else if msg == "get" then reply.right count else if msg == "reset" then reply.right 0 // become (counter 0) else { };Without become, every message sees the initial behaviour. With become, state threads through as a function closure — no mutable variables.
How become works
Section titled “How become works”stateDiagram-v2
direction LR
[*] --> counter_0 : actor(counter 0)
counter_0 --> counter_1 : msg="inc"\nreply={right=1}
counter_1 --> counter_2 : msg="inc"\nreply={right=2}
counter_2 --> counter_2 : msg="get"\nreply={right=2}
counter_2 --> counter_0 : msg="reset"\nreply={right=0}
Each become installs the next function reference as the behaviour. The count is closed over — the function IS the state.
Stateless actors
Section titled “Stateless actors”For pure transformations with no state, map-c is more direct than actor:
inherit (dnzl.ned) map-c;
echo-c = map-c (x: x); # identitydouble-c = map-c (n: n * 2); # scale
# map-c returns a stream transformer — call it on a stream directly(double-c (st 1 2 3)).toList# → [ 2 4 6 ]actor works too, but map-c is the idiomatic form for stateless transforms.
Stateful actors via become
Section titled “Stateful actors via become”A toggle machine — two states, switching on every "flip":
toggle = state: msg: if msg == "flip" then reply.right (!state) // become (toggle (!state)) else if msg == "query" then reply.right state else { };
toggle-c = actor (toggle false);(toggle-c { inbox = st "flip" "query" "flip" "query"; }).outbox.toList# → [ { right = true; } { right = true; } { right = false; } { right = false; } ]Either routing
Section titled “Either routing”reply.right and reply.left tag outbox values. outbox.right and outbox.left are filtered sub-streams:
validator = n: if n > 0 then reply.right n else reply.left "non-positive: ${toString n}";
validator-c = actor validator;
a = validator-c { inbox = st 5 (-3) 8 0 2; };a.outbox.right.toList# → [ 5 8 2 ]a.outbox.left.toList# → [ "non-positive: -3" "non-positive: 0" ]flowchart LR inbox["inbox ST\n[5, -3, 8, 0, 2]"] v["validator-c"] inbox --> v v -->|"reply.right"| right["outbox.right\n[5, 8, 2]"] v -->|"reply.left"| left["outbox.left\n["non-positive: -3", "non-positive: 0"]"]
Chaining on right
Section titled “Chaining on right”outbox.right is a ST of bare success values — tags are stripped. Chain directly into another actor:
inherit (dnzl.ned) map-c st;
double-c = map-c (n: n * 2);validator-c = actor validator;
validated = validator-c { inbox = st 3 (-1) 4 (-2) 7; };
# outbox.right = ST [3, 4, 7] — bare values, no { right = n } wrapperdoubled = double-c validated.outbox.right;doubled.toList# → [ 6 8 14 ]flowchart LR inbox["inbox [3,-1,4,-2,7]"] v["validator-c"] d["double-c\nmap-c (* 2)"] out["[6, 8, 14]"] drop["errors\n(ignored)"] inbox --> v v -->|"outbox.right\n[3, 4, 7]"| d --> out v -->|"outbox.left"| drop style drop fill:none,stroke:#aaa,color:#888