Gleam Trick: Resultify

Posted on .

Here's a short Gleam trick – that I'm actually removing from Scriptorium since it's no longer used there – but that I think deserves a little post anyway.

When you call external functions in the JavaScript target, it is likely they might throw an exception. At least much more likely than in the Erlang target, due to the differences in API design and error handling in the languages. A thrown exception is annoying, since you cannot catch it with Gleam tools directly. This is where the helper comes in.

First we define the Gleam module:

/// A result (of type a) from an external function that can also raise an error
/// (of type b).
///
/// A function that "returns" this type should not be called directly, instead
/// it should be called via `resultify`, which converts the result or exception
/// into a Gleam result.
pub type CanRaise(a, b)

/// Convert a callback function (that should call an external function) from one
/// that can raise to one that will return a Result.
@external(javascript, "/path/to/ffi_exceptions.mjs", "resultify")
pub fn resultify(callback: fn() -> CanRaise(a, b)) -> Result(a, b)

Pair it with this ffi_exceptions.mjs:

import { Ok, Error } from "./gleam.mjs";

export function resultify(callback) {
  try {
    return new Ok(callback());
  } catch (err) {
    return new Error(err);
  }
}

Now let's annotate an external function with the CanRaise type:

@external(javascript, "node:fs", "readFileSync")
fn do_read_file(path: String) -> CanRaise(Buffer, FSError)

This is actually a little white lie. The function actually returns Buffer or throws, but since CanRaise is an external type that we cannot use in Gleam land, it's not that bad of a thing. It does require some discipline to not use this function without resultify, though.

The final step here is to call the external function with resultify:

let path = "..."
use contents <- result.try(exceptions.resultify(fn() { do_read_file(path) }))

With resultify we can call any such external function and use it with the normal Result workflow in Gleam side.

Let me know if you think you have a better way of doing this. I might feature it on the blog!