or_error

Package Version Hex Docs

An OrError is an ad-hoc error type. Use OrError(value) in functions that may fail.

An OrError can be quickly constructed from any type of Result. This makes combining error types from different libraries much easier. However it comes at the cost of being less typeful: you can’t recover the original error value.

Because an OrError is just a Result with a fixed error type, it forms a monad with a single map function, which is useful for effortlessly handling errors in your codebase.

Further documentation can be found at https://hexdocs.pm/or_error.

Comparison of error handling

or_error excels at handling errors of different types.

For a simple comparison, let’s try to read an Int from a json file using simplifile and gleam/dynamic/decode.

Method 1: Nested Results

fn read_int(file_path: String) -> Result(Result(Int, json.DecodeError), simplifile.FileError) {
  use content <- result.map(file_path |> simplifile.read())
  content |> json.parse(decode.int)
}

This method works, but the return type is unweildy at best. Pattern matching on it in future is cumbersome.

Method 2: A custom error type

type FileParseError {
  FileReadError(simplifile.FileError)
  JsonParseError(json.DecodeError)
}

fn read_int(file_path: String) -> Result(Int, FileParseError) {
  use content <- result.try(
    file_path
    |> simplifile.read()
    |> result.map_error(fn(read_error) { FileReadError(read_error) }),
  )

  content
  |> json.parse(decode.int)
  |> result.map_error(fn(parse_error) { JsonParseError(parse_error) })
}

This flattening of the error types provides a much nicer interface for callers to work with. However it takes longer to write and eventually results in huge, nested error variant types.

Method 3: With or_error

If we don’t care to track the error typefully, we can take a pragmatic approach by converting relevant error information to a human-readable string.

fn read_int(file_path: String) -> OrError(Int) {
  use content <-
    file_path
    |> simplifile.read()
    |> or_error.of_result("Failed to read file" <> file_path)
    |> or_error.bind_() // `bind_()` is a pipe-able version of `bind()`

  content
  |> json.parse(decode.int)
  |> or_error.of_result("Failed to parse Int from content")
}

To each or_error.of_result() call, we pass some human-readable context. The error itself will also be recorded; it is constructed by string.inspect(error).

This method scales well as codebases become more complex, and error types more numerable. However, we have lost type information about the errors themselves.

If we were to call or_error.unwrap_panic() with this function on a nonexistent file, we would see:

runtime error: panic

error: Enoent
context: Failed to read file does_not_exist.json

An (almost drop-in) replacement for gleam/result

or_error is intended as a replacement for gleam/result.

As such, all relevant functions are reimplemented, with a few exceptions:

Additionally, some other functions are provided, mostly to make code more pipe-friendly:

Installation

Add or_error to your Gleam project.

gleam add or_error

Prior art

This library is inspired by Ocaml’s Or_error module.

Search Document