🧠
Funkcia Docs
🧠
Funkcia Docs
  • Welcome
  • Data types
    • Option
      • Error Propagation
      • Do Notation
    • Result
      • Error Propagation
      • Do Notation
    • OptionAsync
      • Error Propagation
      • Do Notation
    • ResultAsync
      • Error Propagation
      • Do Notation
  • Modules
    • Exceptions
    • Functions
    • JSON
    • Predicate
    • URI
    • URL
Powered by GitBook
On this page

Was this helpful?

Edit on GitHub
  1. Data types
  2. Option

Error Propagation

PreviousOptionNextDo Notation

Last updated 3 months ago

Was this helpful?

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 , , and 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 the Option value or propagates None.

  • 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 the Option value or propagates None.

  • 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');
});
use expressions
neverthrow's safeTry
Effect's gen