diff --git a/stdlib/effects.affine b/stdlib/effects.affine index 37cb711..95783e5 100644 --- a/stdlib/effects.affine +++ b/stdlib/effects.affine @@ -10,6 +10,13 @@ effect Mut; // Exception effect - panics and error handling effect Throws; +// Async effect - promise / suspend semantics. The Deno-ESM backend +// emits native `async`/`await` and the Thenable extern ABI (issue +// #103); the v1 effect-row registry (#196) already reserves `Async`. +// Declared here so the stdlib effect-declarations file is coherent +// with the registry (echidna#62). Tracking-only in v1 (no handler). +effect Async; + // Built-in IO operations extern fn print(s: String) -> Unit / IO; extern fn println(s: String) -> Unit / IO; diff --git a/stdlib/future.affine b/stdlib/future.affine new file mode 100644 index 0000000..c468741 --- /dev/null +++ b/stdlib/future.affine @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later +// SPDX-FileCopyrightText: 2025 hyperpolymath +// +// Future - async sequencing combinators (echidna#62) +// +// Backs the ReScript->AffineScript migration's async requirement +// (echidna `[migration-roadmap.rescript-to-affinescript]`, Client.res): +// every Client function is `promise>` and chains +// through `Promise.then` / `Promise.catch` / `Promise.resolve`. +// +// THE ASYNC MODEL (read before using) +// ----------------------------------- +// On the migration's Deno-ESM target the compiler emits *native* JS +// `async`/`await` (lib/codegen_deno.ml: all methods are `async`, +// `await` on a synchronous value is valid JS) and host promises cross +// the boundary as the `Thenable` extern ABI (issue #103). Suspension +// therefore happens *at the extern boundary* (echidna#61 `Http` over +// Deno fetch); the AffineScript source never needs a promise monad. +// +// So an "async value" in the migration is just its settled +// `Result` (the `result` half of Client.res's +// `promise>`), carried by a function in the `Async` +// effect (declared in effects.affine; `/{Async}`). These combinators +// are the **value-level** half — a 1:1 map of the ReScript promise +// chain onto that `Result` — and intentionally add no wrapper type: +// +// * AffineScript user-defined generic types are not usable in +// signatures here (compiler kind limitation: a generic `Async` +// ADT/alias raises "Too many arguments for kind"; only prelude's +// `Option`/`Result` are sound generic carriers). `Result` is therefore the carrier, which is also exactly the +// shape Client.res already uses. +// +// ReScript -> this module: +// Promise.resolve(x) -> resolve(x) +// Promise.reject / fail -> reject(e) +// p->Promise.then(f) -> then(p, f) (f : T -> Result) +// p->Promise.thenResolve -> map_ok(p, f) (f : T -> U) +// p->Promise.catch(h) -> recover(p, h) (h : String -> Result) +// error remapping -> map_err(p, f) + +module future; + +use prelude::{ Result, Ok, Err }; + +/// `Promise.resolve(x)` — a settled-successful async value. +pub fn resolve(x: T) -> Result { + Ok(x) +} + +/// A settled-rejected async value (rejection carried as the error +/// string, matching Client.res's `result<_, string>`). +pub fn reject(e: String) -> Result { + Err(e) +} + +/// `Promise.then` over the success channel: run `f` on success, +/// short-circuit an existing rejection. `f` itself yields an async +/// `Result` (so `then` chains async steps, like `.then(x => fetch…)`). +pub fn then(a: Result, f: T -> Result) -> Result { + match a { + Ok(x) => f(x), + Err(e) => Err(e) + } +} + +/// `Promise.then` with a pure mapper (`.then(x => pureValue)` / +/// `thenResolve`): transform the success value, keep rejection. +pub fn map_ok(a: Result, f: T -> U) -> Result { + match a { + Ok(x) => Ok(f(x)), + Err(e) => Err(e) + } +} + +/// `Promise.catch` — recover from a rejection. `h` may itself produce +/// a fresh async `Result` (resolve a fallback, or re-reject). +pub fn recover(a: Result, h: String -> Result) -> Result { + match a { + Ok(x) => Ok(x), + Err(e) => h(e) + } +} + +/// Remap the rejection reason, leaving a success untouched +/// (e.g. wrap a low-level error string with context). +pub fn map_err(a: Result, f: String -> String) -> Result { + match a { + Ok(x) => Ok(x), + Err(e) => Err(f(e)) + } +}