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

# Add to your project root as SKILL.md
curl -o SKILL.md "https://raw.githubusercontent.com/TheBushidoCollective/han/main/plugins/languages/gleam/skills/gleam-interop/SKILL.md"

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

Quality Score

C

Acceptable

71/100

Standard Compliance45
Documentation Quality62
Usefulness78
Maintenance Signal100
Community Signal91
Scored 2d ago

GitHub Signals

Stars100
Forks12
Issues1
Updated2d ago
View on GitHub

Trust & Transparency

No License Detected

Review source code before installing

Verified Open Source

Hosted on GitHub — publicly auditable

Actively Maintained

Last commit 2d ago

100 stars — Growing Community

12 forks

My Fox Den

Community Rating

Works With

Claude Code