Composable Control Flow
Express “stop vs continue” rules in a linear pipeline instead of exception spaghetti.
The issue: “best-effort” rules + exceptions
Real apps rarely treat every failure the same way.
- Invalid discount code: continue without discount.
- Discount service down: abort and ask user to retry.
- Receipt email fails: continue (payment already succeeded).
With exceptions, these rules often become nested try/catch and mutable state.
Bad example: nested try/catch (hard to see the policy)
type = { : string; : number };
class extends {}
class extends {}
class extends {}
class extends {}
function (: string): number {
if ( === "SAVE10") return 10;
if ( === "DOWN") throw new ("discount service down");
throw new ("invalid discount");
}
function (: number): {
if ( === 13) throw new ("gateway down");
return { : "PAY-1", };
}
function (: string): void {
throw new ("smtp down");
}
export function (: string, : number, ?: string): {
try {
if (!) throw new ("userId required");
if (!.() || <= 0) throw new ("amount must be > 0");
let = ;
if () {
try {
= .(0, - ());
} catch () {
if ( instanceof ) {
= ;
} else {
throw new ("Discount unavailable, retry later");
}
}
}
try {
const = ();
try {
(.);
} catch {
// ignored (best-effort)
}
return ;
} catch {
throw new ("Payment failed");
}
} catch () {
throw ;
}
}The fix: make the policy explicit with Result
With @carbonteq/fp, each step returns a value that is either Ok or Err.
You can keep the flow linear and make “continue vs abort” decisions local to the step.
Good example: linear pipeline (policy is obvious)
import { } from "@carbonteq/fp";
type = { : string; : number };
type =
| { : "Validation"; : string }
| { : "DiscountUnavailable" }
| { : "GatewayDown" };
type =
| { : "InvalidCode" }
| { : "Expired" }
| { : "ServiceDown" };
type = { : string; : number };
function (: string, : number): <, > {
if (!)
return .({ : "Validation", : "userId required" });
if (!.() || <= 0)
return .({ : "Validation", : "amount must be > 0" });
return .({ , });
}
function (: string): <number, > {
if ( === "SAVE10") return .(10);
if ( === "EXPIRED") return .({ : "Expired" });
if ( === "DOWN") return .({ : "ServiceDown" });
return .({ : "InvalidCode" });
}
function (: number, ?: string): <number, > {
if (!) return .();
return ()
.(() => .(0, - ))
.(() =>
. === "ServiceDown"
? .({ : "DiscountUnavailable" })
: .() // InvalidCode/Expired: continue without discount
);
}
function (: number): <, > {
if ( === 13) return .({ : "GatewayDown" });
return .({ : "PAY-1", });
}
declare function (: string): void;
export function (: string, : number, ?: string): <, > {
return (, )
.(() => (., ))
.()
.(() => (.)); // best-effort side effect
}