apply
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
| Erlang | JavaScript | |
|---|---|---|
| FFI implementation | src/erl_ffi.erl | src/jst_ffi.mjs |
| Path format | "Module:Function", e.g. "erlang:length" | "object.property", e.g. "Math.max" |
| Target | target = "erlang" in gleam.toml | target = "javascript" in gleam.toml |
Installation
gleam add apply@1
API overview
| Function | Description |
|---|---|
apply/2 | Main entry point: dynamically invoke a runtime function (args must be a tuple) |
get_erl_func/2 | Fetch an Erlang function reference, export checked against arity (Erlang only) |
get_js_obj/1 | Fetch a JavaScript global object / function (JavaScript only) |
call_erl/2, call_js/1 | Quick-check helpers that crash on failure; prefer apply in real code |
platform_name/0 | Current runtime: "erlang" / "javascript" |
is_tuple/1, is_function/1 | Runtime value checks |
do_get/2 | Low-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 needtarget = "javascript".
Return value handling
| Called function returns | Erlang | JavaScript |
|---|---|---|
ok | mapped to Ok(Nil) | returned as-is |
{ok, X} | mapped to Ok(X) | returned as-is |
anything else Other | Ok(Other) | Ok(Other) |
| exception / target missing | Error(message) | Error(message) |
JavaScript performs no
ok/{ok, X}mapping; a function returningundefined(e.g.console.log) maps toOk(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:
| Scenario | erlang | javascript |
|---|---|---|
| Invalid path format | bad path: "a:b", expected "Module:Function" | bad path: "a..b", expected "object.property" |
| Target missing | not_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 arity | erlang:length/2 is not exported in erlang (existing arities: 1) | - (JS does not check arity) |
| Target not callable | error: undef when calling "erlang:length/2" | error: not a function when calling "Math.PI/1" |
| Exception while executing | error: badarg when calling "erlang:length/1" | SyntaxError: ... when calling "JSON.parse/1" |
| Explicit throw / exit | throw: oops when calling "erlang:throw/1", exit: bye when calling "erlang:exit/1" | - |
| Non-tuple arguments | args "oops" must be tuple type | same as left |
| Wrong runtime | need javascript runtime, now is erlang runtime | need erlang runtime, now is javascript runtime |
Notes:
- The Erlang
getchecks exports against the arity and lists the existing arities on mismatch; the JavaScriptgetonly resolves the path and can return any object (not just functions). - Erlang exception reasons are not limited to atoms (
throw/exitcan carry any term) and are formatted in a readable way.
Development
gleam run # Run the project
gleam test # Run the tests
Both test files can stay enabled - tests skip themselves based on the runtime:
test/apply_test.gleam(22 tests) - runs only on the javascript targettest/apply_erlang_test.gleam(24 tests) - runs only on the erlang target
To switch targets, edit gleam.toml and run gleam clean:
# target = "erlang"
target = "javascript"
Further documentation can be found at https://hexdocs.pm/apply/.