FsToolkit.ErrorHandling

repository·master·Indexed 20 days ago

https://github.com/demystifyfp/fstoolkit.errorhandling

A utility library for F# that provides functional error handling patterns centered around the Result type. It implements Railway Oriented Programming (ROP) using computation expressions like asyncResult and asyncOption, utility functions (map, bind, apply, traverse, sequence), and infix operators. The library supports Result, Async<Result>, and various extensions for Task, Job, and AsyncSeq, targeting .NET Standard 2.0, 2.1, and Fable.

Tokens
136.1K
Snippets
539
Records
566
Agent score
68%

What's inside FsToolkit.ErrorHandling

  1. What is FsToolkit.ErrorHandling?

    master

    FsToolkit.ErrorHandling is a utility library for F# designed to simplify error handling using the Result<'a, 'b> type. It provides a suite of functional programming tools to manage success and failure paths without manual pattern matching at every step.

    Key capabilities include:

    • Utility Functions: map, bind, apply, traverse, and sequence.
    • Computation Expressions: Specialized expressions like asyncResult { ... } to compose asynchronous error-prone operations.
    • Infix Operators: For concise manipulation of result types.
    • Supported Types: Works with Result<'a, 'b>, Result<'a option, 'b>, Async<Result<'a, 'b>>, Async<Result<'a option, 'b>>, Result<'a, 'b list>, and (via the TaskResult package) Task<Result<'a, 'b>>.
  2. Overview of FsToolkit.ErrorHandling

    master

    FsToolkit.ErrorHandling is a utility library for F# designed to facilitate clear, simple, and powerful error handling using the Result type. It provides a suite of functional utilities and abstractions to manage success and failure states without relying on exceptions.

    Key features include:

    • Utility Functions: map, bind, apply, traverse, and sequence.
    • Supported Types: Works with Result<'a, 'b>, Result<'a option, 'b>, Async<Result<'a, 'b>>, Async<Result<'a option, 'b>>, and Result<'a, 'b list>.
    • Abstractions: Provides computation expressions and infix operators for composing error-prone logic.
    • Compatibility: Targets .NET Standard 2.0, .NET Standard 2.1, and supports Fable (F# to JavaScript/Python).

    This library is inspired by Railway Oriented Programming (ROP).

  3. Explore FsToolkit.ErrorHandling API modules

    master

    The library is structured into several specialized modules. You can find specific functional combinators (like map, bind, zip, and traverse) within each type's documentation.

    Key functional patterns available across most types include:

    • Mapping: map, map2, map3 to transform successful values.
    • Chaining: bind to sequence computations.
    • Error Handling: mapError, catch, orElse to manage failure paths.
    • Combining: zip, apply, and zipError to merge multiple effects.
    • Sequencing Collections: traverse and sequence functions for Lists, Sequences, and Arrays to turn a collection of effects into an effect containing a collection.
  4. Overview of FsToolkit.ErrorHandling core types

    master

    FsToolkit.ErrorHandling provides a suite of functional error-handling types designed to manage different computational contexts in F#. The core types are organized by the nature of the effect they represent:

    • Result: Represents a computation that can succeed with a value or fail with an error.
    • Option: Represents a value that may or may not be present (similar to Maybe).
    • ResultOption: A combination of Result and Option, representing a value that might be missing, and if present, might contain an error.
    • AsyncResult / AsyncOption: Asynchronous versions of Result and Option (using Task or Async under the hood).
    • Task / TaskResult / TaskOption: Wrappers around the Task type to provide functional combinators.
    • Validation: A type used for accumulating multiple errors rather than short-circuiting on the first failure.
    • JobResult / JobOption: Specialized types for managing background jobs.
    • Cancellable variants: Specialized types (e.g., CancellableTaskResult) that integrate with CancellationToken for cooperative cancellation.
  5. Use the JobOption computation expression

    master

    The jobOption computation expression in the FsToolkit.ErrorHandling namespace allows you to sequence operations that return Job<option<'T>>.

    When using jobOption, the computation expression automatically handles the 'unwrapping' of the option inside the Job. If any step returns a Job containing None, the entire computation expression short-circuits and returns Job<None>. If a step returns Some(value), the let! binding extracts the value for use in subsequent steps. This pattern is useful for workflows where multiple steps might fail to find a result, and you want to stop execution as soon as any step returns None.

    // Assuming these functions exist:
    // tryParseInt : string -> int option
    // tryFindPersonById : int -> Job<Person option>
    // updatePerson : Person -> Job<unit>
    
    // The result type will be Job<unit option>
    let addResult = jobOption {
      let! personId = tryParseInt "3001"
      let! age = tryParseInt "35"
      let! person = tryFindPersonById personId "US-OH"
      let person = { person with Age = age }
      do! updatePerson person
    }
  6. Iterate over IAsyncEnumerable with taskOption

    master

    The taskOption CE supports for .. in .. iteration over IAsyncEnumerable<'T> sequences.

    Behavior: If any iteration step results in a None value, the computation expression immediately stops iteration and returns None. It will not consume any further elements from the sequence once a None is encountered.

    // Task<string option>
    let processItems () =
      taskOption {
        for item in getItemsAsync () do
          do! tryProcessItem item
        return "done"
      }
    // Returns None and stops iteration on the first None result
  7. Iterate over IAsyncEnumerable using taskValueOption

    master

    The taskValueOption CE supports for .. in .. iteration over IAsyncEnumerable<'T> sequences. If any item processed within the loop results in a ValueNone (via a let! or do! binding), the iteration stops immediately, no further elements are consumed, and the computation returns ValueNone.

    // Task<string voption>
    let processItems () =
      taskValueOption {
        for item in getItemsAsync () do
          do! tryProcessItem item
        return "done"
      }
    // Returns ValueNone and stops iteration on the first ValueNone result
  8. Use bind implicitly within a cancellableTaskValidation CE

    master

    The cancellableTaskValidation computation expression (CE) uses let! to perform bind operations implicitly. This is often more readable for complex sequential workflows. If a let! binding fails, the CE short-circuits and returns the error immediately.

    let createUser (input: CreateUserInput) : CancellableTaskValidation<UserId, string> =
        cancellableTaskvalidation {
            let! validatedEmail = validateEmail input.Email
            // bind is used implicitly here — short-circuits if validateEmail fails
            let! userId = createUserRecord validatedEmail input.Name
            return userId
        }
  9. Use TaskValidation for Task<Result<'a, 'b list>> operations

    master
    The TaskValidation module within the FsToolkit.ErrorHandling namespace provides utility functions and infix operators specifically designed to manipulate and compose Task<Result<'a, 'b list>> types. This is useful when you have a task that returns a list of results and you need to perform validation or transformation logic on that collection within a functional error-handling context.
  10. Use the taskResult Computation Expression for asynchronous error handling

    master

    The taskResult computation expression (CE) in the FsToolkit.ErrorHandling namespace allows you to compose asynchronous operations that return Task<Result<'T, 'Error>> values. It provides a clean syntax for sequencing operations where any step returning an Error will short-circuit the entire block and return that error immediately.

    To use it effectively, combine it with helpers like TaskResult.requireSome or Result.requireTrue to transform optional or boolean values into explicit error states within the flow.

    let login (username: string) (password: string) : Task<Result<AuthToken, LoginError>> =
      taskResult {
        let! user = username |> tryGetUser |> TaskResult.requireSome InvalidUser
        do! user |> isPwdValid password |> Result.requireTrue InvalidPwd
        do! user |> authorize |> TaskResult.mapError Unauthorized
        return! user |> createAuthToken |> Result.mapError TokenErr
      }