apply

Package VersionHex Docs

Call functions in the Erlang and JavaScript runtimes at runtime, by path - no @external + *_ffi.erl / *_ffi.mjs boilerplate for every function you want to reach.

import apply
pub fn main() {
let assert Ok(3) = apply.apply("erlang:length", #([1, 2, 3]))
let assert Ok(5) = apply.apply("Math.max", #(1, 5))
}

Resolve a path like "erlang:length" or "Math.max", apply your arguments, and get a Result(any, String) back - with unified, arity-aware error messages on both targets.

Dual-target design

ErlangJavaScript
FFI implementationsrc/erl_ffi.erlsrc/jst_ffi.mjs
Path format"Module:Function", e.g. "erlang:length""object.property", e.g. "Math.max"
Targettarget = "erlang" in gleam.tomltarget = "javascript" in gleam.toml

Installation

gleam add apply@1

API overview

FunctionDescription
apply/2Main entry point: dynamically invoke a runtime function (args must be a tuple)
get_erl_func/2Fetch an Erlang function reference, export checked against arity (Erlang only)
get_js_obj/1Fetch a JavaScript global object / function (JavaScript only)
call_erl/2, call_js/1Quick-check helpers that crash on failure; prefer apply in real code
platform_name/0Current runtime: "erlang" / "javascript"
is_tuple/1, is_function/1Runtime value checks
do_get/2Low-level access, usually not used directly

Usage

Dynamic invocation (apply/2)

raw_path is "Module:Function" on Erlang and "object.property" on JavaScript; args must be a tuple whose elements are spread as call arguments in order.

import apply
pub fn main() {
// ---- Erlang target ----
let assert Ok(3) = apply.apply("erlang:length", #([1, 2, 3]))
let assert Ok(10) = apply.apply("erlang:max", #(10, 2))
let assert Ok("123") = apply.apply("erlang:integer_to_binary", #(123))
// ---- JavaScript target ----
let assert Ok(5) = apply.apply("Math.max", #(1, 5))
let assert Ok("123") = apply.apply("JSON.stringify", #(123))
// Non-tuple arguments return an error (same on both targets)
let assert Error(_) = apply.apply("erlang:length", "not a tuple")
}

Note: the Erlang examples need target = "erlang" and the JavaScript examples need target = "javascript".

Return value handling

Called function returnsErlangJavaScript
okmapped to Ok(Nil)returned as-is
{ok, X}mapped to Ok(X)returned as-is
anything else OtherOk(Other)Ok(Other)
exception / target missingError(message)Error(message)

JavaScript performs no ok/{ok, X} mapping; a function returning undefined (e.g. console.log) maps to Ok(Nil) in Gleam.

Fetching objects / function references

// Erlang target only: export checked against arity; errors list existing arities
let assert Ok(f) = apply.get_erl_func("lists:map", 2)
let assert Error(msg) = apply.get_erl_func("lists:map", 3)
// msg == "lists:map/3 is not exported in erlang (existing arities: 2)"
// JavaScript target only: resolve a dotted path on globalThis (no arity check)
let assert Ok(_) = apply.get_js_obj("Math.max")
let assert Ok(3.141592653589793) = apply.get_js_obj("Math.PI")

Runtime checks

apply.platform_name() // "erlang" or "javascript"
apply.is_tuple(#(1, 2)) // True
apply.is_function(fn() { 1 }) // True

Error message format

Both targets share a class: reason when calling "path/arity" style:

Scenarioerlangjavascript
Invalid path formatbad path: "a:b", expected "Module:Function"bad path: "a..b", expected "object.property"
Target missingnot_a_module:foo/1 not found in erlang (module or function does not exist)Math.notExist not found in javascript (object or property does not exist)
Not exported at that arityerlang:length/2 is not exported in erlang (existing arities: 1)- (JS does not check arity)
Target not callableerror: undef when calling "erlang:length/2"error: not a function when calling "Math.PI/1"
Exception while executingerror: badarg when calling "erlang:length/1"SyntaxError: ... when calling "JSON.parse/1"
Explicit throw / exitthrow: oops when calling "erlang:throw/1", exit: bye when calling "erlang:exit/1"-
Non-tuple argumentsargs "oops" must be tuple typesame as left
Wrong runtimeneed javascript runtime, now is erlang runtimeneed erlang runtime, now is javascript runtime

Notes:

Development

gleam run # Run the project
gleam test # Run the tests

Both test files can stay enabled - tests skip themselves based on the runtime:

To switch targets, edit gleam.toml and run gleam clean:

# target = "erlang"
target = "javascript"

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