|
| 1 | +# fpRust — Project Notes: Origins, Identity & Less-Known Capabilities |
| 2 | + |
| 3 | +> Durable engineering notes derived from a git-history + API-surface investigation |
| 4 | +> (204 commits, 2018‑07 → 2026‑07). This document records *what kind of project this |
| 5 | +> is*, *why it is shaped the way it is*, and the *public capabilities that are easy to |
| 6 | +> miss*. It is context for maintainers and advanced users; the [README](../README.md) |
| 7 | +> remains the primary user guide and [ROADMAP](../ROADMAP.md) the maintenance posture. |
| 8 | +
|
| 9 | +## Identity |
| 10 | + |
| 11 | +fpRust is an **educational, polyglot functional-programming / reactive pattern |
| 12 | +library** — the Rust member of a tri-language family alongside **`fpGo`** (Go) and |
| 13 | +**`fpEs`** (ECMAScript). Its APIs deliberately mirror those siblings (`src/maybe.rs` |
| 14 | +documents the `map`/`chain`/`ap` parity), so the same FP/reactive concepts read the |
| 15 | +same way across Go, JavaScript, and Rust. |
| 16 | + |
| 17 | +It is **not** a production async runtime or a Tokio/Actix competitor. It optimizes for |
| 18 | +**clarity and teachability** of cross-language patterns, and is a good fit for |
| 19 | +*learning & prototyping* and *selective module use* — not *production orchestration* |
| 20 | +(no unified shutdown protocol; see [ROADMAP](../ROADMAP.md) exclusions). |
| 21 | + |
| 22 | +## Origin story (three eras) |
| 23 | + |
| 24 | +| Era | Window | What landed | Influences | |
| 25 | +|-----|--------|-------------|------------| |
| 26 | +| **1 — Sync FP + threading core** | 2018‑07 (142 commits) | `Maybe` → `MonadIO` → `Handler`/`HandlerThread` → `BlockingQueue` → `Publisher`/`SubscriptionFunc` → `CountDownLatch` → `Cor` → fp macros (`map!`/`reduce!`/`filter!`/`fold*!`/`compose!`/`pipe!`) → `do_m!` | Haskell (monads, do‑notation), Android (`Handler` loop), Java `java.util.concurrent`, RxJava schedulers, Go coroutines (Cor "from golang version"), GoF Observer (via `eliovir/rust-examples`), `dtolnay/reduce` | |
| 27 | +| **2 — Java Future** | 2018‑08 (3 commits) | `WillAsync` ("to achieve JavaFuture") | Java `Future` callback/wait semantics | |
| 28 | +| **3 — Async Rust + Actors** | 2021‑06→08 (56 commits) | Rust‑2021 modernization (only external PR ever, #1 `bogdad/fix_compilation`), `futures::Stream` + `Future` integration, `subscribe_as_stream`, real async `CountDownLatch`, `BlockingQueue::{take,poll}_result_as_future`, **Akka‑style `ActorAsync`** (ask/tell + children), `LinkedListAsync`, feature‑flag modularization | |
| 29 | + |
| 30 | +(2026‑07 added test coverage, docs/roadmap, and async‑waker correctness fixes — see [CHANGELOG](../CHANGELOG.md).) |
| 31 | + |
| 32 | +### On the word "leaked" |
| 33 | +The 2018‑07‑11 commit **"Add leaked observe/subscribe_on"** is the author's own term |
| 34 | +for **capabilities shipped ahead of their documentation** — public, working, but never |
| 35 | +surfaced to users. This document is largely about paying down that gap. |
| 36 | + |
| 37 | +## Less-known / "leaked" capabilities |
| 38 | + |
| 39 | +These are fully public and working but absent from the README's main capability list. |
| 40 | +Ranked by user value; every claim traced to source. |
| 41 | + |
| 42 | +### 1. `MonadIO::observe_on` / `subscribe_on` — scheduler routing (with a trap) |
| 43 | +`src/monadio.rs:105,110`. Inspired by RxJava's `observeOn`/`subscribeOn`, but the |
| 44 | +semantics **differ** — verified at `src/monadio.rs:141‑163`: |
| 45 | + |
| 46 | +- `ob_handler == None` → the effect runs **synchronously on the calling thread** and |
| 47 | + **`subscribe_on` is ignored entirely**. So `subscribe_on` *alone* is a silent no‑op. |
| 48 | +- `ob_handler == Some` → the effect is posted to the observe handler, which then |
| 49 | + forwards effect execution **and** the subscriber's `on_next` to the subscribe |
| 50 | + handler thread (`Some`) or runs them on the observe thread (`None`). |
| 51 | + |
| 52 | +**Rule:** if you want thread‑hopping, always set `observe_on` first; `subscribe_on` |
| 53 | +only takes effect when `observe_on` is set. |
| 54 | + |
| 55 | +### 2. `Publisher::subscribe_blocking_queue` / `as_blocking_queue` — push→pull bridge |
| 56 | +`src/publisher.rs:99,107`. Turns the push‑based pub/sub stream into a **pull‑based |
| 57 | +`BlockingQueue`**: published values are forwarded into a queue a consumer thread drains |
| 58 | +on its own schedule (`queue.take()` / `take_result_as_future().await`). Ideal when the |
| 59 | +consumer wants to pull deterministically instead of running in a callback. Note: it is |
| 60 | +a delivery bridge, not a bounded backpressure mechanism — see the queue's own limits. |
| 61 | + |
| 62 | +### 3. `Publisher::subscribe_on` — off-thread delivery |
| 63 | +`src/publisher.rs:58`. Unlike MonadIO's, this one behaves straightforwardly: with a |
| 64 | +handler set, `notify_observers` posts each delivery to that handler thread |
| 65 | +(`src/publisher.rs` notify path); with `None`, delivery is synchronous on the publish |
| 66 | +caller. Safe to use on its own. |
| 67 | + |
| 68 | +### 4. `BlockingQueue::{take,poll}_result_as_future` — sync↔async bridge |
| 69 | +`src/sync.rs`. `for_futures` only. Offloads the blocking `recv` onto the shared futures |
| 70 | +thread pool so a synchronous `BlockingQueue` can be `.await`ed from async code. |
| 71 | + |
| 72 | +### 5. `do_m_pattern!` — pattern-matching do-notation |
| 73 | +`src/cor.rs:51`. A richer `do_m!`: supports typed `let`, `let mut`, reassignment, |
| 74 | +`exec` side-effect statements, `yield_from` of coroutines, and a final `ret`. Live and |
| 75 | +tested (`test_cor_do_m_pattern*`, cor.rs:470/578/590) — **not** dead code. |
| 76 | + |
| 77 | +### 6. Utility macros absent from the README |
| 78 | +- `map_insert!(m, { k: v, ... })` / `[ k => v, ... ]` (`src/common.rs`) — bulk |
| 79 | + `HashMap` population in a language without map literals (several syntaxes). |
| 80 | +- `cor_newmutex!`, `cor_newmutex_and_start!`, `cor_yield!` (`src/cor.rs`) — the actual |
| 81 | + macros for building/driving coroutines. |
| 82 | +- `contains!`, `reverse!` (`src/fp.rs`) — curried `Vec` helpers. |
| 83 | +- `spread_and_call!`, `partial_left_last_one!` (`src/fp.rs`) — currying **plumbing** |
| 84 | + behind the fp macros; internal, not intended as user entry points. |
| 85 | + |
| 86 | +## Scenarios & better ways to use |
| 87 | + |
| 88 | +### MonadIO — deferred/lazy IO with scheduler hopping |
| 89 | +*When:* defer a computation and run it off-thread, delivering the result on another |
| 90 | +thread. *Better way:* set **both** `observe_on` and `subscribe_on` (see the trap in |
| 91 | +§1); start both handlers before `subscribe`. Runnable: `examples/scheduler.rs` |
| 92 | +(proves the routing and the `subscribe_on`-alone no-op by asserting `ThreadId`); |
| 93 | +`examples/monadio.rs` shows the basic sync/async subscribe. |
| 94 | + |
| 95 | +### Publisher — Rx pub/sub, three consumption styles |
| 96 | +Callback (`subscribe_fn`), **pull** (`as_blocking_queue` → `take`), or **stream** |
| 97 | +(`subscribe_as_stream` → `collect().await`). Choose pull when the consumer sets the |
| 98 | +pace; stream when composing with `futures`. Runnable: `examples/publisher_queue.rs` |
| 99 | +(push→pull bridge). |
| 100 | + |
| 101 | +### Cor — coroutines & do-notation for sequencing |
| 102 | +*When:* sequence dependent steps without nesting. Use `do_m!`/`do_m_pattern!`. |
| 103 | +**Gotcha (verified invariant):** a `yield_from` target must be **async** |
| 104 | +(`set_async(true)`, the default for `cor_newmutex_and_start!`). Two coroutines that |
| 105 | +yield to each other while both sync **deadlock** — keep the entry coroutine sync and |
| 106 | +targets async (see README "Cor sync deadlock invariant" and `src/cor.rs`). |
| 107 | + |
| 108 | +### Actor — Akka-style ask/tell with children |
| 109 | +`examples/actor.rs` (spawn + child lifecycle) and `examples/actor_ask.rs` (reply via a |
| 110 | +`BlockingQueue`, using a take-timeout, not sleep). Shutdown is per-actor and manual — |
| 111 | +do not assume structured teardown. |
| 112 | + |
| 113 | +### Anti-patterns (grounded in this codebase) |
| 114 | +1. **`thread::sleep` for synchronization** → use `CountDownLatch::wait()`, a |
| 115 | + `BlockingQueue` take, or `to_future().await`. (This session removed 7 such sleeps; |
| 116 | + examples are sleep-free.) |
| 117 | +2. **`subscribe_on` without `observe_on` in MonadIO** → silent no-op (§1). |
| 118 | +3. **Sync `yield_from` target** → deadlock; keep targets async. |
| 119 | + |
| 120 | +## Cross-language family `[INFERENCE]` |
| 121 | +`fpGo` (Go) and `fpEs` (ECMAScript) are the sibling ports; `Cor` was ported "from |
| 122 | +golang version". Specific fpGo/fpEs API details are **inferred** from this repo's |
| 123 | +comments/commits and are not verified against those repositories here. |
| 124 | + |
| 125 | +## Provenance (attributions) |
| 126 | +Haskell (monads, do-notation) · Java `java.util.concurrent` (`BlockingQueue`, |
| 127 | +`CountDownLatch`, `Will`/future) · Android `Handler` loop (`HandlerThread`) · RxJava |
| 128 | +schedulers (`observe_on`/`subscribe_on`) · Go coroutines (`Cor`) · Akka/Erlang |
| 129 | +(`ActorAsync` ask) · `dtolnay/reduce` (`Reduce`) · `eliovir/rust-examples` (Observer). |
| 130 | +The `compose!`/`pipe!` macros were reimplemented from first principles this session to |
| 131 | +remove a CC BY-SA snippet. See [CONTRIBUTING](../CONTRIBUTING.md#source-credit-and-provenance). |
0 commit comments