diff options
Diffstat (limited to 'ocaml/EFFECTS.md')
| -rw-r--r-- | ocaml/EFFECTS.md | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/ocaml/EFFECTS.md b/ocaml/EFFECTS.md new file mode 100644 index 0000000..1c5db5d --- /dev/null +++ b/ocaml/EFFECTS.md @@ -0,0 +1,305 @@ +# Effects System in Neovere + +## What Are Effects? + +When Arvo processes an event via poke, it returns: +``` +[effects new_core] +``` + +Where `effects` is a list of effects to execute in the outside world. + +## Effect Structure + +Each effect is a pair: `[wire card]` + +- **Wire**: Path identifying the request (for response routing) +- **Card**: The actual effect to execute + +Example effects: +``` +[/d/term/1 [%blit [%lin [%& "hello"]]]] // Print "hello" to terminal +[/d/term/1 [%blit [%mor ...]]] // Multiple blits +[/g/http/0v123 [%http-response ...]] // HTTP response +[/a/peer/~zod [%send ...]] // Network packet +``` + +## Effect Types (Cards) + +### Terminal (Dill) +- `%blit` - Print to terminal + - `%lin` - Line of styled text + - `%klr` - Colored/styled text + - `%mor` - Multiple blits + - `%hop` - Move cursor + - `%clr` - Clear screen +- `%logo` - Show logo/sigil + +### HTTP (Eyre) +- `%http-response` - Send HTTP response +- `%response` - Generic response + +### Network (Ames) +- `%send` - Send packet to another ship +- `%turf` - DNS/networking config + +### Filesystem (Clay) +- `%ergo` - File change notification +- `%hill` - Mount/unmount + +### Timer (Behn) +- `%doze` - Set timer +- `%wake` - Timer fired + +## Our Approach: Eio-Based Effects Processing + +### Architecture + +``` +┌─────────────────────────────────────────┐ +│ Event Loop (Eio) │ +│ - Read terminal input │ +│ - Handle timers │ +│ - Network I/O │ +└─────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Poke Arvo with Event │ +│ result = State.poke state event │ +└─────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Parse Effects from Result │ +│ match result with │ +│ | Cell (effects, new_core) -> ... │ +└─────────────────┬───────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Route Effects to Drivers │ +│ - Dill: terminal I/O │ +│ - Eyre: HTTP server (future) │ +│ - Ames: networking (future) │ +└─────────────────────────────────────────┘ +``` + +### Phase 1: Terminal (Dill) Driver + +**Goal:** Boot a ship and see output in the terminal + +**What we need:** +1. Parse `%blit` effects +2. Render styled text to ANSI terminal +3. Read keyboard input (belt events) +4. Poke Arvo with belt events +5. Display blit effects + +**Implementation:** +```ocaml +(* lib/effects.ml *) +module Effect = struct + type blit = + | Lin of string (* Simple line *) + | Klr of styled_text (* Colored text *) + | Mor of blit list (* Multiple *) + | Hop of int (* Cursor move *) + | Clr (* Clear screen *) + + type card = + | Blit of blit + | Logo + | Unknown of Noun.noun + + type t = { + wire: Noun.noun; + card: card; + } + + let parse_effect noun = ... + let parse_effects effects_noun = ... +end + +(* lib/dill.ml *) +module Dill = struct + let render_blit = function + | Effect.Lin text -> + Printf.printf "%s\n%!" text + | Effect.Klr styled -> + (* Convert to ANSI codes *) + render_styled styled + | Effect.Mor blits -> + List.iter render_blit blits + | Effect.Hop n -> + (* ANSI cursor move *) + ... + | Effect.Clr -> + (* ANSI clear screen *) + Printf.printf "\x1b[2J\x1b[H%!" + + let handle_effects effects = + effects + |> Effect.parse_effects + |> List.filter (fun e -> is_dill_wire e.wire) + |> List.iter (fun e -> + match e.card with + | Effect.Blit b -> render_blit b + | _ -> ()) +end +``` + +### Phase 2: Event Loop with Eio + +```ocaml +(* bin/neovere_live.ml *) +open Eio.Std + +let run_ship env state = + let stdin = Eio.Stdenv.stdin env in + let clock = Eio.Stdenv.clock env in + + (* Start terminal reader fiber *) + Fiber.both + (fun () -> + (* Read keyboard input *) + while true do + let line = Eio.Flow.read_line stdin in + let belt_event = Dill.make_belt_event line in + let effects = State.poke state belt_event in + Dill.handle_effects effects + done) + (fun () -> + (* Timer fiber *) + while true do + Eio.Time.sleep clock 1.0; + (* Handle doze/wake *) + done) +``` + +### Phase 3: Structured Concurrency + +Eio's strength is **structured concurrency** - no callback hell! + +```ocaml +let handle_http_request request = + (* Run in fiber, naturally concurrent *) + Switch.run @@ fun sw -> + (* Create ovum for HTTP request *) + let ovum = make_http_ovum request in + (* Poke Arvo *) + let effects = State.poke state ovum in + (* Parse effects, find HTTP response *) + match find_http_response effects with + | Some response -> send_response response + | None -> send_404 () + +let main env = + (* All concurrent activities in one place *) + Switch.run @@ fun sw -> + Fiber.all [ + terminal_input_loop env sw; + timer_loop env sw; + http_server env sw; + ames_network env sw; + ] +``` + +## Implementation Plan + +### Sprint 1: Basic Effects ✓ (Next!) +- [ ] Create `lib/effects.ml` - effect parsing +- [ ] Parse `[effects new_core]` from poke result +- [ ] Parse individual effects `[wire card]` +- [ ] Parse blit cards +- [ ] Test by logging parsed effects + +### Sprint 2: Dill Driver +- [ ] Create `lib/dill.ml` - terminal I/O +- [ ] Render %blit effects to terminal +- [ ] Handle styled text (ANSI codes) +- [ ] Read keyboard input +- [ ] Create belt events from input +- [ ] Round-trip: input → poke → effects → output + +### Sprint 3: Event Loop +- [ ] Create `bin/neovere_live.ml` - interactive runtime +- [ ] Eio-based event loop +- [ ] Terminal input fiber +- [ ] Effects processing fiber +- [ ] Graceful shutdown + +### Sprint 4: Full Terminal Experience +- [ ] Handle all blit types (hop, clr, etc.) +- [ ] Proper cursor control +- [ ] Line editing support +- [ ] History/readline integration +- [ ] Status line + +## Testing Strategy + +### Test 1: Parse Effects +```ocaml +(* After boot, send a test event and parse effects *) +let test_event = make_test_event "hello" in +let result = State.poke state test_event in +match result with +| Cell (effects, _) -> + let parsed = Effect.parse_effects effects in + List.iter (fun e -> + Printf.printf "Effect: wire=%s card=%s\n" + (show_wire e.wire) + (show_card e.card) + ) parsed +``` + +### Test 2: Dill Roundtrip +```ocaml +(* Send belt, receive blit *) +let belt = Dill.make_belt_event "~zod/try/1" in +let result = State.poke state belt in +Dill.handle_effects result +(* Should print something to terminal! *) +``` + +### Test 3: Interactive Session +```bash +$ dune exec bin/neovere_live.exe zod + +~zod:dojo> (add 2 2) +4 +~zod:dojo> "hello world" +"hello world" +~zod:dojo> +``` + +## Eio Advantages for Urbit + +1. **Structured Concurrency** - All fibers have clear lifetime +2. **Backpressure** - Natural flow control +3. **Cancellation** - Clean shutdown of all I/O +4. **Multicore** - Can use multiple cores for I/O +5. **Type Safety** - Eio ensures proper resource management + +## Effect Wire Routing + +Wires tell us where effects came from: + +``` +/d/term/1 -> Dill (terminal 1) +/g/http/... -> Eyre (HTTP) +/a/peer/~zod -> Ames (network to ~zod) +/c/sync/... -> Clay (filesystem) +/b/wait/... -> Behn (timers) +``` + +We route by examining the wire structure. + +## Next Steps + +1. **Now:** Create basic effect parsing +2. **Next:** Dill blit rendering +3. **Then:** Keyboard input + belt events +4. **Finally:** Full event loop with Eio + +Let's start with `lib/effects.ml`! |
