Skip to content
Skill

Gleam Erlang Interop

by TheBushidoCollective

AI Summary

Guides developers on calling Erlang code from Gleam, leveraging the BEAM ecosystem while maintaining type safety. Essential for Gleam developers integrating with Erlang libraries or NIFs.

Install

Copy this and paste it into Claude Code, Cursor, or any AI assistant:

I want to install the "Gleam Erlang Interop" skill in my project.

Please run this command in my terminal:
# Install skill into the correct directory
mkdir -p .claude/skills/gleam-interop && curl --retry 3 --retry-delay 2 --retry-all-errors -o .claude/skills/gleam-interop/SKILL.md "https://raw.githubusercontent.com/TheBushidoCollective/han/main/plugins/languages/gleam/skills/gleam-interop/SKILL.md"

Then restart Claude Code (or reload the window in Cursor) so the skill is picked up.

Description

Use when gleam-Erlang interoperability including calling Erlang code from Gleam, using Erlang libraries, external functions, working with Erlang types, NIFs, and leveraging the BEAM ecosystem from Gleam applications.

Introduction

Gleam compiles to Erlang, enabling seamless interoperability with the vast Erlang ecosystem. This interop allows Gleam developers to leverage battle-tested Erlang libraries while writing type-safe Gleam code, combining modern language features with decades of production-proven libraries. The interop mechanism uses external functions to call Erlang code, with Gleam's type system providing safety at the boundary. Gleam can call any Erlang function, use Erlang processes, and integrate with OTP behaviors while maintaining type safety. This skill covers external function declarations, calling Erlang modules, working with Erlang types, using Erlang standard library, NIFs and ports, and patterns for safe type boundaries when integrating with Erlang code.

External Function Declarations

External functions declare Erlang functions with Gleam types, providing typed interfaces to Erlang code. `gleam // Basic external function @external(erlang, "erlang", "list_to_binary") pub fn list_to_binary(list: List(Int)) -> BitArray // Using external functions pub fn convert_list() { let list = [72, 101, 108, 108, 111] let binary = list_to_binary(list) io.debug(binary) } // External function with multiple arguments @external(erlang, "string", "concat") pub fn string_concat(a: String, b: String) -> String pub fn join_strings() { let result = string_concat("Hello, ", "World!") io.debug(result) } // External function returning Result @external(erlang, "file", "read_file") fn erlang_read_file(path: String) -> Result(BitArray, Dynamic) pub fn read_file(path: String) -> Result(String, String) { case erlang_read_file(path) { Ok(contents) -> { case bit_array.to_string(contents) { Ok(str) -> Ok(str) Error(_) -> Error("Invalid UTF-8") } } Error(_) -> Error("File read error") } } // External function with erlang types @external(erlang, "maps", "get") fn map_get(key: a, map: Map(a, b)) -> Result(b, Nil) @external(erlang, "maps", "put") fn map_put(key: a, value: b, map: Map(a, b)) -> Map(a, b) // Using Erlang's timer module @external(erlang, "timer", "sleep") pub fn sleep(milliseconds: Int) -> Nil pub fn wait_a_second() { io.println("Waiting...") sleep(1000) io.println("Done!") } // External function with tuples @external(erlang, "calendar", "local_time") pub fn local_time() -> #(#(Int, Int, Int), #(Int, Int, Int)) pub fn get_current_time() { let #(date, time) = local_time() let #(year, month, day) = date let #(hour, minute, second) = time io.debug(#(year, month, day, hour, minute, second)) } // External functions with atoms pub type Atom @external(erlang, "erlang", "binary_to_atom") fn binary_to_atom(binary: BitArray) -> Atom @external(erlang, "erlang", "atom_to_binary") fn atom_to_binary(atom: Atom) -> BitArray // Creating safe wrappers pub opaque type ErlangAtom { ErlangAtom(atom: Dynamic) } pub fn atom_from_string(str: String) -> ErlangAtom { let bits = bit_array.from_string(str) let atom = binary_to_atom(bits) ErlangAtom(atom: dynamic.from(atom)) } // External function with callbacks @external(erlang, "lists", "map") pub fn erlang_map(f: fn(a) -> b, list: List(a)) -> List(b) pub fn double_list(list: List(Int)) -> List(Int) { erlang_map(fn(x) { x * 2 }, list) } // Process-related external functions @external(erlang, "erlang", "self") pub fn self() -> process.Pid @external(erlang, "erlang", "spawn") pub fn spawn(f: fn() -> Nil) -> process.Pid // System external functions @external(erlang, "erlang", "system_time") fn system_time_nanos() -> Int pub fn current_timestamp() -> Int { system_time_nanos() / 1_000_000 } ` External declarations bridge Gleam's type system with Erlang's dynamic runtime.

Using Erlang Standard Library

Gleam can leverage Erlang's comprehensive standard library for various operations. `gleam // Crypto module @external(erlang, "crypto", "hash") fn crypto_hash(algorithm: Atom, data: BitArray) -> BitArray pub fn sha256(data: String) -> BitArray { let algo = atom_from_string("sha256") let bits = bit_array.from_string(data) crypto_hash(algo.atom, bits) } pub fn md5(data: String) -> String { let algo = atom_from_string("md5") let bits = bit_array.from_string(data) let hash = crypto_hash(algo.atom, bits) bit_array.base16_encode(hash, False) } // Random module @external(erlang, "rand", "uniform") fn uniform() -> Float @external(erlang, "rand", "uniform") fn uniform_int(n: Int) -> Int pub fn random_float() -> Float { uniform() } pub fn random_int(max: Int) -> Int { uniform_int(max) } pub fn random_choice(list: List(a)) -> Option(a) { case list { [] -> None items -> { let index = uniform_int(list.length(items)) list.at(items, index - 1) } } } // ETS (Erlang Term Storage) pub type EtsTable @external(erlang, "ets", "new") fn ets_new(name: Atom, options: List(Atom)) -> EtsTable @external(erlang, "ets", "insert") fn ets_insert(table: EtsTable, tuple: #(a, b)) -> Bool @external(erlang, "ets", "lookup") fn ets_lookup(table: EtsTable, key: a) -> List(#(a, b)) pub fn create_cache() -> EtsTable { let name = atom_from_string("my_cache") let public = atom_from_string("public") let set = atom_from_string("set") ets_new(name.atom, [set.atom, public.atom]) } pub fn cache_put(table: EtsTable, key: String, value: String) -> Bool { ets_insert(table, #(key, value)) } pub fn cache_get(table: EtsTable, key: String) -> Option(String) { case ets_lookup(table, key) { [#(_, value)] -> Some(value) _ -> None } } // HTTP client via Erlang's httpc @external(erlang, "httpc", "request") fn httpc_request(url: String) -> Result(Dynamic, Dynamic) pub fn http_get(url: String) -> Result(String, String) { case httpc_request(url) { Ok(response) -> { // Parse Erlang response tuple Ok("Response received") } Error(_) -> Error("HTTP request failed") } } // JSON via jiffy library @external(erlang, "jiffy", "encode") fn jiffy_encode(term: Dynamic) -> BitArray @external(erlang, "jiffy", "decode") fn jiffy_decode(json: BitArray) -> Result(Dynamic, Dynamic) pub fn encode_json(data: Dict(String, String)) -> String { let dynamic_data = dynamic.from(data) let bits = jiffy_encode(dynamic_data) case bit_array.to_string(bits) { Ok(str) -> str Error(_) -> "{}" } } // OS module @external(erlang, "os", "getenv") fn os_getenv(var: String) -> Result(String, Nil) pub fn get_env(var: String) -> Option(String) { case os_getenv(var) { Ok(value) -> Some(value) Error(_) -> None } } @external(erlang, "os", "cmd") pub fn shell_command(cmd: String) -> String pub fn list_files() -> String { shell_command("ls -la") } // Code loading @external(erlang, "code", "ensure_loaded") fn code_ensure_loaded(module: Atom) -> Result(Atom, Atom) pub fn ensure_module_loaded(module_name: String) -> Bool { let atom = atom_from_string(module_name) case code_ensure_loaded(atom.atom) { Ok(_) -> True Error(_) -> False } } ` Erlang's standard library provides production-tested functionality for common operations.

Working with Erlang Types

Gleam's Dynamic type enables safe interop with Erlang's dynamic type system. `gleam import gleam/dynamic // Dynamic value handling pub fn handle_dynamic(value: Dynamic) -> String { case dynamic.string(value) { Ok(str) -> "String: " <> str Error(_) -> case dynamic.int(value) { Ok(n) -> "Int: " <> int.to_string(n) Error(_) -> "Unknown type" } } } // Decoding Erlang tuples pub fn decode_tuple(value: Dynamic) -> Result(#(String, Int), List(dynamic.DecodeError)) { dynamic.tuple2(dynamic.string, dynamic.int)(value) } // Decoding Erlang lists pub fn decode_string_list(value: Dynamic) -> Result(List(String), List(dynamic.DecodeError)) { dynamic.list(dynamic.string)(value) } // Complex Erlang term decoder pub type Person { Person(name: String, age: Int, email: Option(String)) } pub fn person_decoder() -> dynamic.Decoder(Person) { dynamic.decode3( Person, dynamic.field("name", dynamic.string), dynamic.field("age", dynamic.int), dynamic.optional_field("email", dynamic.string), ) } pub fn decode_person(value: Dynamic) -> Result(Person, List(dynamic.DecodeError)) { person_decoder()(value) } // Encoding to Erlang terms pub fn person_to_dynamic(person: Person) -> Dynamic { dynamic.from([ #("name", dynamic.from(person.name)), #("age", dynamic.from(person.age)), #("email", case person.email { Some(email) -> dynamic.from(email) None -> dynamic.from(Nil) }), ]) } // Working with Erlang records (tuples) pub fn erlang_record_decoder( tag: String, ) -> fn(Dynamic) -> Result(List(Dynamic), List(dynamic.DecodeError)) { fn(value) { use tuple <- result.try(dynamic.tuple(value)) use first <- result.try(case list.at(tuple, 0) { Ok(elem) -> dynamic.string(elem) Error(_) -> Error([dynamic.DecodeError("Expected tuple with string tag", "")]) }) case first == tag { True -> Ok(list.drop(tuple, 1)) False -> Error([dynamic.DecodeError("Wrong record tag", "")]) } } } // Proplist (Erlang key-value list) handling pub type Proplist = List(#(Dynamic, Dynamic)) pub fn proplist_get(proplist: Proplist, key: String) -> Option(Dynamic) { list.find_map(proplist, fn(pair) { let #(k, v) = pair case dynamic.string(k) { Ok(str) if str == key -> Ok(v) _ -> Error(Nil) } }) |> result.to_option } // Atom handling via dynamic pub fn decode_atom(value: Dynamic) -> Result(String, List(dynamic.DecodeError)) { case dynamic.stri

Discussion

0/2000
Loading comments...

Health Signals

MaintenanceCommitted 1mo ago
Active
Adoption100+ stars on GitHub
100 ★ · Growing
DocsREADME + description
Well-documented

GitHub Signals

Stars100
Forks12
Issues1
Updated1mo ago
View on GitHub
No License

My Fox Den

Community Rating

Sign in to rate this booster

Works With

Claude Code