實驗性練習專案:用 Ruby 重實作 Cordis(TS plugin/生命週期 meta-framework)的核心機制。不求生產可用,型別安全不是考量重點。優先順序是「機制正確」而不是 API 表面覆蓋率。
上游 repo 本機位置:/Users/ryudo/ghq/github.com/cordiverse/cordis(對接目標是 packages/core,4.0.0-rc.8)。注意 4.0 改名:EffectScope → Fiber、Lifecycle → EventsService。
理論來自論文 "A Programming Paradigm for Spatiotemporal Composability"。不確定正式定義時,查 NotebookLM(內有 paper.pdf、cordis GitHub、deepseek-harness GitHub),不要憑印象猜:
notebooklm ask "..." --notebook b4683395-8795-47c8-9d7a-76f058954f06
- Revertible effect:effect 為 Γ → Γ × (Γ → Γ)。
Fiber#effect的 block 立即套用副作用、回傳 inverse(callable / callable 陣列 / Enumerator);@disposablesstack 就是 φ accumulator,unload 嚴格 LIFO 撤除。 - Theorem 16:單一 component 內嚴格 LIFO 即可正確復原 non-commutative 效果。關鍵區分是 commutative vs non-commutative,不是「新增 vs 修改」。
- Reactive coeffect(已實作):
Fiber#inject(宣告)+@internal_store(目前被滿足的 provider)+ epoch 字串指紋(各 provider fiber uid;缺一個 → INACTIVE)。Context#notify線性掃描 →check_impl→refresh→set_epoch。provider 換掉 → epoch 變 → 強制 reload。provider 卸載時,provide 的 disposer 先 notify、等所有依賴者卸載完,才刪 provider 自見的 entry —— 依賴者 teardown 順序由機制保證。 - Plugin 即 effect:
ctx.plugin把 plugin 的 dispose 當成一個 effect 掛在 parent fiber 上(上游 fiber.ts:170),整棵 plugin tree 就是巢狀 effect tree,沒有獨立的 teardown 路徑。
- API 命名仿 Cordis(使用者確認):
ctx.effect/ctx.plugin/ctx.provide/ctx.inject/ctx.on。Cordis::Fiber刻意與 Ruby core::Fiber撞名以對齊上游 4.0(Cordis 內部不直接用 core Fiber)。Context#extend刻意遮蔽Object#extend。 - round 2 破壞性變更:
ctx.extend對齊上游語意 = 「同 root、不同 fiber」的 ctx 視角,不再是新 disposal scope;新 scope 一律來自ctx.plugin。round 1 的DisposedError移除,改InactiveEffectError/ServiceError(訊息對齊上游)。 - 併發用 async gem(Fiber-based),不用 Thread+Mutex。Promise 語意在 Ruby 對應為「task 內直接 block」:需要 reactor 的 API(
plugin/dispose/await/parallel)必須在Sync/Async內呼叫,reactor 外會 raise "No async task available!"。 - 載入延後一個 tick(對應上游
await Promise.resolve()):async 的 child task 是 eager,所以 transition task 開頭Async::Task.current.yield。 - inertia lock:每個 fiber 一個 transition task,loop 到 current epoch == target epoch 為止;transition 中 target 變更只記意圖。
restart必須先await卸載完再refresh,否則 target 設回原 epoch 會把 unload 意圖合併掉(已踩過)。 - 與上游的刻意差異(
# ponytail:註記):unload 是嚴格 LIFO「循序」撤除(上游是 LIFO 啟動 + 併發完成);plugin 回傳值只在 callable 時收為 disposer(上游_execute會對非法值 TypeError)。 - isolate/intercept(round 3):isolate key 預設就是 name 本身(不學上游
root[isolate][name] ??= Symbol的 lazy 建 key),isolated ctx 才在自己的@isolatemap 放 token(Object.new或使用者傳的 label);map 是 copy-on-write merge,不用 prototype chain。service store 改以 key 為鍵、Fiber#provided改{name => key}、notify 收{name => key}pairs 並過濾fib.ctx.isolate_key(n) == key。intercept 同樣 copy-on-write,鏈在ctx.intercept時就地 merge 攤平(上游是 resolveConfig 走 prototype chain 收集再 assign),讀取入口是ctx.resolve_config(name, base);Hash 形式的 inject config 會轉成 plugin ctx 上的 intercept(對齊 fiber.ts:139)。比上游嚴格的一處:service walk 在終點 fiber 對 provided 服務檢查 realm key(上游 name-keyed store 會讓 isolated plugin 走到 root 時漏看到 default realm 的 service)。 - Service base class(round 4):
Cordis::Service走 Ruby class DSL ——provide :name/inject :dep(或inject dep: {config})取代上游的super(ctx, name)與static inject;#initialize(ctx, config)內ctx.provide(name, self),#init對應上游Service.init(在 fiber loading 期間執行,block 住即 pending inject)。registry 支援 Class plugin(對應上游 isConstructor):runtime 以 class 為 key(Runtime#key新欄位,remove 用它刪),callback 包一層plugin.new(ctx, config)+instance.init。跳過:traceable proxy 相關(ctx重綁、Service[filter]isolated event、[extend]callable service)、provider-sidecheckpredicate(上游 spec 沒測,要再說)。無 proxy 所以 per-caller config 要顯式傳 caller ctx 給 service method(見 demo.rb AuditLog)。class ivar 不繼承,巢狀繼承的 provide/inject 不支援(ponytail)。/Users/ryudo/RubyPrjs/lens-ruby-async:完成訊號用Async::Queue不用Async::Condition(signal 先於 wait 會遺失)、spec 用task.with_timeout包可能吊死的流程(spec_helper 的with_reactor)、teardown 逐步各自 rescue。
已完成:effect protocol、Fiber 狀態機(pending/loading/active/failed/disposed/unloading)、ctx.plugin、events(on/once/emit/bail/waterfall/parallel/serial)、reactive coeffect(provide/inject/get/set)。
明確排除(之後再說):isolate/intercept、logger service、traceable/shadow proxy、callable service、dotted service names、config validation、Service base class、loader/hmr/timer packages。
- 測試:RSpec,
bundle exec rspec;spec/ 大致鏡射上游packages/core/tests/(dispose/plugin/events/service/fiber/reflect)。 - commit 前只對變更的 .rb 檔逐一跑
rubocop -A(不要對 .yml 等非 Ruby 檔跑);.rubocop.yml已關 Metrics 與 Naming/AccessorMethodName(set_epoch/set_state對齊上游命名)。 - commit message 以英文為主。