Skip to content

Commit 001bd38

Browse files
committed
refine effects concept
1 parent c0b8271 commit 001bd38

File tree

4 files changed

+14
-26
lines changed

4 files changed

+14
-26
lines changed

docs/make-it-act.md

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,31 +86,25 @@ That’s the entire simulation loop.
8686

8787
## Expanding the Model: Effects
8888

89-
Once you’re comfortable with **commands** and **events**, you can add a third kind of message: **effects**.
89+
Once you’re comfortable with **commands** and **events**, a third kind of message enters the picture: **effects**.
9090

91-
An **effect** is an outbound request — something your component wants the outside world to do. Thus, a command, but for an indeterminate actor.
91+
Here’s the twist — from the perspective of a component, effects don’t actually exist. A component never concerns itself with what the outside world should do. It has no awareness beyond its own boundaries.
9292

93-
It’s still just a message, but it’s aimed beyond the component’s boundary.
93+
All authority flows downward. A component doesn’t reach upward to speculate or orchestrate. It owns its work and reports what happened — that’s what events are for.
9494

95-
### Why start without them
95+
**Events describe facts. Commands express intent.**
9696

97-
Most of the time, you don’t need effects.
98-
The shell can simply **observe state changes** and react.
97+
Effects, then, are not new kinds of messages. They’re **derived**. When a higher-level authority observes an event, it may choose to respond by sending new commands to its subordinates. This translation — from observed event to triggered command — is what we call an effect.
9998

100-
If a game’s status changes to `"conceded"`, the UI or another process can notice and respond.
101-
That’s the **implicit model** — a simpler way to start. Graduating to the **explicit model** is optional.
99+
In other words:
102100

103-
### Modeling effects explicitly
104-
105-
If you need a record or queue of outbound work, the actor can accumulate effects internally.
106-
You can inspect them with `glance()` and clear them with `drain()`:
101+
```
102+
Event → (orchestration logic) → Command(s)
103+
```
107104

108-
| Method | Purpose |
109-
| ------------------ | -------------------------------------------- |
110-
| **`glance(self)`** | View staged effects awaiting routing. |
111-
| **`drain(self)`** | Return the actor with those effects cleared. |
105+
The orchestration layer, whether a parent component or mediator, performs that translation. It observes child events, decides what they mean, and dispatches new commands downward in response.
112106

113-
This lets the shell — or perhaps some **mediator** — monitor the actor, pick up its messages, and route them elsewhere.
107+
Effects are therefore not things a component *emits* — they are commands *triggered* elsewhere in response to events. Events may continue bubbling up, giving higher authorities the context they need to direct their own domains.
114108

115109
## Dependency Inversion
116110

@@ -195,16 +189,16 @@ This model is a hybrid, mostly pure, but there's no `Either` monad for accommoda
195189

196190
I use these throws to uphold rules and reject invalid requests. They act as guardrails, signaling that the command shouldn’t proceed. From there, the **shell** catches them — treating each as though it were just another effect.
197191

198-
If you wanted to push through to total purity, it wouldn’t take much. You could surface those failures as data instead of exceptions — registering them through the same `effects` API (`glance`, `drain`) and letting a handler route or record them as structured problems.
192+
If you wanted to push through to total purity, it wouldn’t take much. You could surface those failures in the component state instead of exceptions — and allow them to bubble up as error events which, in turn, produce effects.
199193

200194
As always in software, there’s freedom in the tradeoff. You can handle rejection as data or as control flow. For now, I lean toward throwing — a small concession to impurity in service of keeping things simple.
201195

202196
## Summary
203197

204198
You began with a simple atom you swapped functions against. Now you’re swapping **messages**.
205199

206-
At first, there are only two: **commands** (intentions) and **events** (facts). That’s enough to build a fully simulated, replayable world.
200+
There are only two: **commands** (intentions) and **events** (facts). That’s enough to build a fully simulated, replayable world.
207201

208-
Later, you can implement **effects** — outbound messages for the world — using an implicit (simpler) or explicit (staged and drained) model. You can even inject impure handlers to interpret those effects directly.
202+
You can always generate whatever **effects**, commands for subordinates, you deem useful.
209203

210204
Everything still happens *inside* the atom. The component remains a simulation — you just taught it how to **act**.

src/core/protocols/iactor/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ Represents an actor which receives and processes commands.
66
* `actuate(self, event)` - receives an event and folds it into the state.
77
* `undone(self, event)` - can the event be undone?
88
* `events(self)` - returns all known events
9-
* `glance(self)` - returns effects accumulated for external actors, but not yet drained
10-
* `drain(self)` - returns the actor without the effects
119

1210
The `undone` check isn’t about realtime undo/redo — that’s what [journals](../../types/journal/) handle. It’s about reversibility as a system-level feature: can the user ask the system to roll this action back? Keep in mind that as more actions accumulate, something reversible now may later become irreversible. Events—often logged in the backend—record an `undoable` flag to track this capability.
1311

src/core/protocols/iactor/concrete.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import {IFunctor} from "../ifunctor/instance.js";
44
import {overload} from "../../core.js";
55

66
export const act = IActor.act;
7-
export const glance = IActor.glance;
8-
export const drain = IActor.drain;
97
export const events = IActor.events;
108
export const undone = IActor.undone;
119

src/core/protocols/iactor/instance.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@ export const IActor = protocol({
33
act: null,
44
actuate: null,
55
undone: null,
6-
glance: null,
7-
drain: null,
86
events: null
97
});

0 commit comments

Comments
 (0)