Error Propagation
Funkcia offers a concise and convenient way to write your code in a more imperative style that utilizes the native scope provided by the generator syntax. This syntax is more linear and resembles normal synchronous code.
Drawing primarily from Rust's ?
operator for error propagation, and inspired by Gleam's use
expressions, neverthrow's safeTry
, and Effect's gen
functions, the following functions provide a clean way to handle sequential operations while maintaining proper error handling and type safety.
use
Evaluates a generator early returning when an Option.None
is propagated or returning the Option
returned by the generator.
Each
yield*
automatically unwraps theOption
value or propagatesNone
.If any operation returns
Option.None
, the entire generator exits early.
import { Option } from 'funkcia';
declare const safeParseInt: (string: string, radix?: number) => Option;
// ββββ Option
// βΌ
const option = Option.use(function* () {
const x: number = yield* safeParseInt('10');
const y: number = yield* safeParseInt('invalid'); // breaks the circuit, returning Option.None
return Option.some(x + y); // doesn't run
});
// Output: None
createUse
Returns a function that evaluates a generator when called with the declared arguments, early returning when an Option.None
is propagated or returning the Option
returned by the generator.
Each
yield*
automatically unwraps theOption
value or propagatesNone
.If any operation returns
Option.None
, the entire generator exits early.
import { Option } from 'funkcia';
declare const safeParseInt: (string: string, radix?: number) => Option<number>;
// ββββ (a: string, b: string) => Option<number>
// βΌ
const sumParsedIntegers = Option.createUse(function* (a: string, b: string) {
const x: number = yield* safeParseInt(a);
const y: number = yield* safeParseInt(b);
return Option.some(x + y);
});
const option = sumParsedIntegers('10', '20');
// Output: Some(30)
Understanding the use method
The use
method provides a way to write sequential operations that might fail, similar to Rust's ?
operator. It lets you write code that looks synchronous while safely handling potential failures.
It essentially creates a "safe context" where you can work with values as if they were guaranteed to exist, while maintaining all the safety guarantees of Option
. If anything fails, the failure propagates automatically. Like an electronic relay that controls current flow, β relay controls computation flow: β Option.Some
continues, β Option.None
breaks the circuit.
Here's a practical example:
import { Option } from 'funkcia';
declare function findUser(id: string): Option;
declare function getUserPermissions(user: User): Option;
declare function checkAccess(permissions: Permissions, resource: string): Option;
const access = Option.use(function* () {
// First, try to find the user
const = yield* findUser('user_123');
// If user is found (`findUser` returns `Option.Some(User)`, get their permissions
const permissions = yield* getUserPermissions(user);
// If all steps succeed, we can use the accumulated context to check access to specific resource
return checkAccess(permissions, 'api-key');
});
The equivalent code without use
would be much more nested:
import { Option } from 'funkcia';
declare function findUser(id: string): Option<User>;
declare function getUserPermissions(user: User): Option<Permissions>;
declare function checkAccess(permissions: Permissions, resource: string): Option<Access>;
const access = findUser('user_123')
.andThen(user =>
getUserPermissions(user)
.andThen(permissions =>
checkAccess(permissions, 'api-key')
)
);
Or with intermediate variables:
import { Option } from 'funkcia';
declare function findUser(id: string): Option<User>;
declare function getUserPermissions(user: User): Option<Permissions>;
declare function checkAccess(permissions: Permissions, resource: string): Option<Access>;
const maybeUser = findUser('user_123');
const maybePermissions = maybeUser.andThen(getUserPermissions);
const access = maybePermissions.andThen(permissions => {
return checkAccess(permissions, 'api-key');
});
Last updated
Was this helpful?