Can PureScript's Effect and Aff monads be unified into a single effect-parameterized monad?
0 reputation · 19 Apr 2025, 20:24 UTC
0 reputation · 19 Apr 2025, 20:24 UTC
When designing a new PureScript library, I need to decide whether to expose both Effect and Aff interfaces or to bet on a future unified effect monad. The Effect type tracks synchronous side effects through a phantom effect row, while Aff layers asynchronous, cancellable computations on top of that foundation.
Today the language keeps these monads distinct, so moving a synchronous computation into an async context requires an explicit lift. An unresolved language-design discussion considers merging them into a single effect-parameterized monad to reduce boilerplate, but no consensus has been reached. Without knowing the direction, it is unclear whether maintaining two parallel APIs is a durable investment or temporary overhead.
Which trade-offs are driving the debate between preserving the explicit Effect/Aff boundary and pursuing unification? Does the existing phantom-row design already permit a combined monad without breaking backward compatibility? What criteria would the compiler team likely use to accept or reject such a change?
No, PureScript's Effect and Aff cannot be unified into a single effect-parameterized monad today without breaking changes. The phantom effect row tracks synchronous capabilities only; Aff's asynchronous scheduling, cancellation, and fiber semantics are runtime constructs that the row system does not capture. A true unification would require encoding async/cancellation into the type system and rewriting the runtime—a major design shift with no consensus.
Effect (in purescript-effect) and Aff (in purescript-aff) are separate newtypes with different constructors and no shared unified interface.liftEffect :: Effect a -> Aff a. The boundary is intentional to keep blocking vs. non-blocking costs visible in types.Effect/Aff split makes synchronous side effects and asynchronous cancellable work explicitly distinct. Conflating them risks accidental blocking in async code and hides performance characteristics.Aff uses a fiber scheduler with cancellation tokens, timeout handling, and parallel composition (parAff). These are not expressible as phantom row labels; they require a dedicated runtime.CONSOLE, RANDOM), not how they execute. Adding async/cancellation to the row would turn a lightweight capability system into a full effect system with runtime implications.ASYNC, CANCEL).Aff runtime to interpret those capabilities instead of using its current fiber machinery.Effect and Aff API, breaking all downstream libraries.This is why the existing phantom-row design does not permit a combined monad without breaking backward compatibility. A new unified monad could be introduced alongside the current two, but replacing them is a breaking change of ecosystem scale.
| Criterion | Likely Weight |
|---|---|
| Backward compatibility | High — breaking core types requires a migration path for the entire ecosystem |
| Runtime performance | High — any unification must not degrade Aff's scheduling or cancellation guarantees |
| Type-level expressiveness | Medium — row system must cleanly model async/cancellation without exploding complexity |
| Boilerplate reduction | Low — convenience is a benefit, not a justification for breaking change |
| Community consensus | High — major type-system changes go through RFC process with broad review |
Design for the current split. Expose both Effect and Aff interfaces where appropriate, and use liftEffect at the boundary. This is stable, well-understood, and matches the ecosystem's direction. If a unified monad ever lands, it will likely arrive as an opt-in addition—not a replacement—giving libraries time to adopt gradually.
If you are targeting a specific framework (e.g., purescript-halogen, purescript-react-basic) that already provides its own effect abstraction or unified monad, check its current API. Some frameworks layer their own effect types on top of Aff and may already offer the unification you want without waiting for core changes.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.