You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/make-it-act.md
+14-20Lines changed: 14 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,31 +86,25 @@ That’s the entire simulation loop.
86
86
87
87
## Expanding the Model: Effects
88
88
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**.
90
90
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.
92
92
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.
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.
99
98
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:
102
100
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()`:
|**`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.
112
106
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.
114
108
115
109
## Dependency Inversion
116
110
@@ -195,16 +189,16 @@ This model is a hybrid, mostly pure, but there's no `Either` monad for accommoda
195
189
196
190
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.
197
191
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.
199
193
200
194
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.
201
195
202
196
## Summary
203
197
204
198
You began with a simple atom you swapped functions against. Now you’re swapping **messages**.
205
199
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.
207
201
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.
209
203
210
204
Everything still happens *inside* the atom. The component remains a simulation — you just taught it how to **act**.
Copy file name to clipboardExpand all lines: src/core/protocols/iactor/README.md
-2Lines changed: 0 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,8 +6,6 @@ Represents an actor which receives and processes commands.
6
6
*`actuate(self, event)` - receives an event and folds it into the state.
7
7
*`undone(self, event)` - can the event be undone?
8
8
*`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
11
9
12
10
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.
0 commit comments