Gleam's Resource Management Revolution

How Gleam's use syntax transforms code structure

Gleam's use expression is syntactic sugar that converts callback-style code into linear, readable sequences.

// The pattern:

use <variables> <- <function taking a callback>

// as in:

use user <- result.try(authenticate(credentials))`

follows a precise structure where the left side declares variables receiving callback arguments, while the right side must be a function taking a callback as its final parameter.

Traditional nested approach:

pub fn login(credentials) {
  case authenticate(credentials) {
    Error(e) -> Error(e)
    Ok(user) -> case fetch_profile(user) {
      Error(e) -> Error(e)
      Ok(profile) -> render_welcome(user, profile)
    }
  }
}

With use expression:

pub fn login(credentials) {
  use user <- result.try(authenticate(credentials))
  use profile <- result.try(fetch_profile(user))
  render_welcome(user, profile)
}

The transformation is profound: deeply nested error handling becomes linear, top-to-bottom code flow while preserving functional programming principles. Under the hood, the compiler transforms use expressions into regular function calls with anonymous functions, ensuring zero performance overhead.

Gleam's design philosophy and unique benefits

What sets Gleam's approach apart is its generality and simplicity. Unlike language-specific constructs (async/await for concurrency, try/catch for errors), use works with any function accepting a callback as the final argument. This creates a unified syntax for multiple programming patterns:

Resource management:

pub fn process_file() {
  use file <- with_file("data.txt")
  use connection <- with_database()
  process_data_with_resources(file, connection)
}

List comprehensions:

pub fn cartesian_product() {
  use letter <- list.flat_map(["a", "b", "c"])
  use number <- list.map([1, 2, 3])
  #(letter, number)
}

The key insight is that callback hell appears across many domains - error handling, resource management, list processing, and async operations. Rather than creating specialized syntax for each domain, Gleam provides one elegant solution that works universally.

Elegant TypeScript implementations

TypeScript offers several sophisticated approaches to replicate Gleam's elegance, each with different trade-offs between complexity and functionality.

Native resource management with using syntax

TypeScript 5.2's explicit resource management provides the closest direct equivalent to Gleam's resource handling:

class DatabaseConnection implements Disposable {
  [Symbol.dispose]() {
    this.close();
  }
}

async function processData() {
  using connection = new DatabaseConnection();
  using file = new FileHandle("data.txt");

  // Resources automatically disposed when scope exits
  return await processDataWithResources(connection, file);
}

This approach offers zero overhead and perfect TypeScript integration while maintaining the automatic cleanup benefits of Gleam's use.

Effect-TS: The comprehensive functional solution

Effect-TS emerges as the most sophisticated option, providing Gleam-like capabilities with advanced features. Its generator-based syntax closely mirrors Gleam's linear flow:

import { Effect } from "effect";

const processUser = (id: string) =>
  Effect.gen(function* (_) {
    const user = yield* _(Effect.tryPromise(() => fetchUser(id)));
    yield* _(Effect.log(`Processing user: ${user.name}`));
    const validUser = yield* _(Effect.tryPromise(() => validateUser(user)));
    const result = yield* _(Effect.tryPromise(() => saveUser(validUser)));
    return result;
  });

Effect-TS provides dependency injection, structured concurrency, and observability while maintaining the linear code flow that makes Gleam's use so appealing.

Lightweight monad implementations

For projects preferring minimal dependencies, custom Result types offer elegant error handling without external libraries:

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

const chain = <T, U, E>(
  result: Result<T, E>,
  fn: (value: T) => Result<U, E>,
): Result<U, E> => result.ok ? fn(result.value) : result;

// Gleam-style sequential operations
const processFile = (filename: string): Result<string, string> =>
  chain(
    readFile(filename),
    (content) =>
      chain(processContent(content), (processed) =>
        chain(validateOutput(processed), (validated) =>
          ok(validated))),
  );

This approach provides type-safe error propagation with minimal overhead, closely matching Gleam's error handling semantics.

Generator-based do-notation

Generator functions create the most Gleam-like syntax for sequential operations:

function* doM<T>(gen: Generator<any, T, any>): Generator<any, T, any> {
  return yield* gen;
}

const processData = () =>
  run(doM(function* () {
    const user = yield* fetchUser();
    const profile = yield* fetchProfile(user.id);
    const settings = yield* fetchSettings(profile.id);

    return processUserData(user, profile, settings);
  }));

This pattern eliminates callback nesting while maintaining the sequential, linear flow that makes Gleam's use so readable.

Comparative advantages across languages

Gleam's approach occupies a unique position in the landscape of error handling and resource management. Rust's ? operator provides similar error propagation but only works with Result/Option types, while Haskell's do-notation requires deep understanding of monadic abstractions. F#'s computation expressions offer more flexibility but at the cost of complexity.

Gleam strikes an optimal balance between power and simplicity. The use syntax works with any callback-taking function, making it more general than Rust's approach while being more accessible than Haskell's mathematical foundations.

Recommendations for TypeScript teams

For simple resource management: Use TypeScript 5.2's native using syntax, which provides automatic cleanup with zero learning curve and perfect tooling integration.

For comprehensive functional programming: Effect-TS offers the most complete solution, providing Gleam-like ergonomics plus advanced features like structured concurrency and dependency injection.

For lightweight error handling: Custom Result types with chaining functions provide excellent type safety and performance while requiring minimal dependencies.

For teams new to functional programming: Generator-based do-notation offers familiar syntax while introducing functional concepts gradually.

Conclusion

Gleam's use syntax represents a paradigm shift in functional programming ergonomics. By focusing on the "happy path" and providing a general solution to callback hell, it makes sophisticated resource management and error handling accessible without sacrificing the benefits of functional programming.

TypeScript's ecosystem offers multiple elegant paths to achieve similar functionality, from native language features to comprehensive functional libraries. The choice depends on project complexity, team experience, and specific requirements, but each approach can deliver the readability, type safety, and maintainability that makes Gleam's design so compelling.

The key insight from Gleam's approach is that simplicity and power aren't mutually exclusive. By providing a single, general-purpose syntax that works across multiple domains, Gleam demonstrates how thoughtful language design can make complex programming patterns both elegant and accessible.