Master Erlang Extraction in Gleam: Complete Learning Path
Master Erlang Extraction in Gleam: Complete Learning Path
Gleam's Erlang Extraction is a powerful feature for safely deconstructing and pattern-matching on data from Erlang or Elixir. It is the primary mechanism for interoperability on the BEAM virtual machine, allowing you to interact with existing libraries and frameworks in a type-safe, idiomatic way.
The Interop Nightmare: Why Erlang Extraction is Your Savior
Imagine you're building a new, high-performance service. You chose Gleam for its incredible type safety and modern developer experience. However, your existing infrastructure is a robust Elixir monolith, rich with battle-tested libraries for everything from database access with Ecto to real-time communication with Phoenix.
You write a Gleam function to call an Elixir module. The Elixir function returns a classic Erlang-style tuple: {:ok, user_data} or {:error, :not_found}. Back in your Gleam code, how do you handle this? Without a proper mechanism, you'd be fumbling in the dark, dealing with opaque, untyped data. This is where bugs hide and runtime errors thrive.
This is the exact pain point that Erlang Extraction solves. It's not just a feature; it's the secure, type-safe bridge between Gleam's world of guarantees and the dynamic, powerful ecosystem of the BEAM. It lets you look inside these Erlang and Elixir data structures and handle them with the full power of Gleam's pattern matching, turning chaos into predictable, safe code.
What Exactly is Erlang Extraction?
At its core, Erlang Extraction is a specialized syntax within Gleam's case expression designed for interoperability with other BEAM languages like Erlang and Elixir. While standard Gleam pattern matching works on Gleam's own custom types and built-in data structures, Erlang Extraction allows you to pattern match on the internal structure of terms originating from the Erlang world.
Think of it as a special lens. When you receive a value from an external Erlang or Elixir function, Gleam initially sees it as an opaque, dynamic type. Erlang Extraction is the lens that lets you peer inside that value, identify its shape—is it a 2-element tuple starting with the atom :ok? Is it an Elixir struct? Is it an Erlang record?—and safely extract the data within.
This is accomplished using special syntax, often involving the erlang module, which provides constructors that mirror common Erlang patterns. For example, instead of matching on a Gleam Ok(value), you might match on erlang.Ok(value) to deconstruct an Erlang {:ok, value} tuple.
import gleam/io
import gleam/erlang
// This function is defined in an external Erlang/Elixir module
@external(erlang, "external_library", "fetch_user")
fn fetch_user(id: Int) -> erlang.Term
pub fn main() {
case fetch_user(1) {
// Here is the Erlang Extraction in action!
erlang.Ok(user_data) -> {
// We safely extracted `user_data` from the {:ok, ...} tuple
io.println("Successfully fetched user data.")
// We can now work with user_data
}
erlang.Error(erlang.Atom("not_found")) -> {
// We matched on the {:error, :not_found} tuple
io.println("Error: User not found.")
}
_ -> {
// A catch-all for any other unexpected response
io.println("An unknown error occurred.")
}
}
}
In the example above, erlang.Ok(user_data) is not a function call. It's a pattern. It tells the Gleam compiler: "If the term fetch_user(1) is a tuple with two elements, and the first element is the atom ok, then bind the second element to the variable user_data." This is the essence of safe interoperability.
Why is This Bridge So Crucial for Gleam?
Gleam's design philosophy prioritizes static analysis and type safety to eliminate entire classes of runtime errors. However, it also aims to be a practical language that leverages existing ecosystems. Since Gleam compiles to Erlang (for the BEAM target), it would be severely limited if it couldn't communicate effectively with the vast universe of Erlang and Elixir code.
The Pillars of Necessity:
- Leveraging the BEAM Ecosystem: The BEAM VM has been developed and hardened for decades. It powers some of the world's most reliable systems (telecom, banking, messaging). Libraries like Phoenix (web framework), Ecto (database wrapper), and the entire OTP framework are invaluable assets. Erlang Extraction is the key that unlocks this treasure chest for Gleam developers.
- Maintaining Type Safety: The alternative to extraction would be to treat all external data as a generic, untyped blob. This would force developers to write unsafe, defensive code, undermining Gleam's core value proposition. Extraction provides a controlled, safe "airlock" to bring external data into Gleam's typed world.
- Gradual Adoption: Companies with large Erlang or Elixir codebases can adopt Gleam incrementally. They can rewrite critical components in Gleam for added safety while leaving the rest of the application intact. This is only possible because of seamless, safe interoperability, which is powered by Erlang Extraction.
-
Idiomatic Error Handling: The
{:ok, value}/{:error, reason}tuple is a deeply ingrained convention in the BEAM world. Erlang Extraction allows Gleam code to handle these idiomatic return values naturally and safely, making the code easier to read for developers coming from Erlang or Elixir.
How to Wield Erlang Extraction: Syntax and Patterns
Understanding the mechanics of Erlang Extraction is key to mastering it. The functionality is primarily exposed through patterns within a case expression, often using functions and types from the gleam/erlang standard library module.
1. Matching on Ok/Error Tuples
This is the most common use case. You're calling a function that returns either a success or error tuple.
import gleam/erlang
import gleam/erlang/atom
// A fictional external function
@external(erlang, "file_api", "read_file")
fn read_file(path: String) -> erlang.Term
pub fn process_file(path: String) {
case read_file(path) {
// Matches {:ok, file_content}
erlang.Ok(content) -> {
// `content` is now bound and can be used.
// Note: `content` is still an erlang.Term here.
// You might need further decoding.
assert Ok(text) = erlang.binary_to_string(content)
// now `text` is a Gleam String
}
// Matches {:error, reason_atom}
erlang.Error(reason) if erlang.is_atom(reason) -> {
let reason_string = atom.to_string(erlang.unsafe_coerce(reason))
io.println("Failed to read file: " <> reason_string)
}
// A catch-all is always a good practice
_ -> io.println("Received an unexpected return value.")
}
}
Notice the use of a guard clause: if erlang.is_atom(reason). This adds another layer of safety, ensuring we only try to convert the reason to a string if it's actually an atom, preventing a crash if the error reason was, for example, a string or an integer.
2. The Deconstruction Flow
Here is a conceptual flow of how a Gleam program processes an external BEAM term using extraction.
● Start: Receive erlang.Term
│
▼
┌───────────────────────────┐
│ `case` expression begins │
└────────────┬──────────────┘
│
▼
◆ Is it an `{:ok, data}` tuple?
(erlang.Ok(data))
╱ ╲
Yes ◀───────────────────────▶ No
│ │
▼ ▼
┌──────────────────┐ ◆ Is it an `{:error, reason}` tuple?
│ Bind `data` │ (erlang.Error(reason))
│ to a variable │ ╱ ╲
└──────────────────┘Yes ◀───────────────────────▶ No
│ │
▼ ▼
┌──────────────────┐ ◆ Does it match any
│ Bind `reason` │ other pattern?
│ to a variable │ ╱ ╲
└──────────────────┘Yes ◀────────────────▶ No
│ │
▼ ▼
[Process Match] [Default Case `_`]
│ │
└─────────┬──────────┘
▼
● End of `case`
3. Extracting from Elixir Structs (Erlang Maps)
Elixir structs are just Erlang maps with a special __struct__ key. Gleam can match on these maps directly. To do this, you'll often use external type definitions to give these structures a name in Gleam.
Let's say an Elixir library gives you a %User{name: "Ada", age: 30} struct.
// In Gleam, we can define an opaque type to represent this.
// The `@external` attribute tells Gleam this type is defined elsewhere.
@external(erlang, "Elixir.User", "")
pub type User
// We define functions to access the fields. These are "getters".
@external(erlang, "maps", "get")
pub fn get_name(user: User) -> erlang.Term
@external(erlang, "maps", "get")
pub fn get_age(user: User) -> erlang.Term
// An external function that returns this Elixir struct
@external(erlang, "user_service", "get_user_struct")
fn get_user_struct() -> User
pub fn main() {
let user_struct = get_user_struct()
// We have the struct, now let's safely get the data
let name_term = get_name(atom.create_from_string("name"), user_struct)
let age_term = get_age(atom.create_from_string("age"), user_struct)
// The values are still erlang.Term, so we must decode them
let assert Ok(name) = erlang.binary_to_string(name_term)
let assert Ok(age) = erlang.term_to_int(age_term)
io.println("User: " <> name <> ", Age: " <> int.to_string(age))
}
This approach is more about defining FFI (Foreign Function Interface) bindings than direct pattern matching on the map structure within a case, but it's the idiomatic way to handle complex external types like structs. Direct map pattern matching is also possible for simpler cases but can become verbose.
4. The Struct Deconstruction Mental Model
This diagram shows how you can think about accessing data inside an Elixir struct from Gleam.
Elixir World Gleam World
──────────── ───────────
%User{ ● erlang.Term
name: "Ada", │ (Opaque Struct)
age: 30, │
} ▼
┌──────────────────────────────┐
│ Call FFI getter for `:name` │
│ `maps.get(atom("name"), term)` │
└──────────────┬───────────────┘
│
▼
● erlang.Term (Binary: <<"Ada">>)
│
▼
┌───────────────────────────┐
│ Decode into Gleam type │
│ `erlang.binary_to_string` │
└───────────┬───────────────┘
│
▼
● Gleam `String` ("Ada")
Where and When to Apply This Technique
Knowing a tool is one thing; knowing when to use it is another. Erlang Extraction is not something you'll use in pure, self-contained Gleam applications. Its purpose is explicitly for crossing the language boundary on the BEAM.
Prime Use Cases:
- Calling Elixir Libraries: Any time you use
@externalto call an Elixir function from a library like Ecto, Phoenix, or Oban, you will almost certainly need Erlang Extraction to handle the return value. - Interacting with OTP: When building systems that interface with OTP primitives like
GenServer,Supervisor, orAgent, you'll be sending and receiving Erlang terms. Extraction is essential for safely interpreting messages and state. - Handling Phoenix Channel/Controller Payloads: If your Gleam code is part of a Phoenix web application, the data coming from HTTP requests or WebSocket messages will be in the form of Elixir terms (often maps). You'll need extraction to process this data.
- Database Results: When using a library that wraps an Erlang database driver (like for PostgreSQL or MySQL), the rows returned will be Erlang terms (tuples or maps) that need to be deconstructed.
Pros & Cons Analysis
Like any powerful feature, it's important to understand its trade-offs.
| Pros | Cons & Risks |
|---|---|
|
|
Start Your Practical Journey
Theory is essential, but practice is where mastery is forged. The kodikra.com learning path provides a hands-on module specifically designed to solidify your understanding of this crucial concept.
In this module, you will work through a practical problem that requires you to receive and deconstruct common Erlang data structures, applying the patterns we've discussed here to build a robust and safe function.
- Learn Erlang Extraction step by step: Dive into the code and apply your knowledge to solve a real-world interop challenge.
By completing this exercise from our exclusive curriculum, you'll move from theoretical knowledge to practical confidence, ready to integrate Gleam seamlessly into any BEAM-based project.
Frequently Asked Questions (FAQ)
What is the difference between Gleam's `case` and Erlang Extraction?
Standard Gleam case expressions pattern match on Gleam's own types, like Result, custom type constructors, lists, and tuples defined within Gleam. Erlang Extraction is a special set of patterns used inside a case expression, specifically to match the structure of data coming from Erlang/Elixir, such as {:ok, ...} tuples or records.
Is Erlang Extraction truly type-safe?
It provides a "safe boundary." The safety comes from the compiler forcing you to handle different patterns. If you write a case expression that covers all expected shapes of the incoming erlang.Term and includes a default _ case, your code is safe. However, if your patterns are incorrect (e.g., you expect a tuple but get a list), a runtime crash will occur in that branch. The "safety" is in making the handling of different data shapes explicit.
Can I use this feature when compiling Gleam to JavaScript?
No. Erlang Extraction is specific to the Erlang/BEAM compile target. The entire concept is built around interoperability with the Erlang VM and its data types. When compiling to JavaScript, you would use a different set of tools and patterns for interoperability with JavaScript libraries, typically involving the gleam/dynamic module or specific FFI bindings for JS.
What is the `erlang.Term` type?
erlang.Term is Gleam's representation of any possible value that can exist in the Erlang VM. It's a top-level, dynamic type, similar to any or Object in other languages. You cannot do much with a Term directly; its purpose is to be inspected and deconstructed with a case expression to turn it into a more specific, useful Gleam type.
How do I handle nested Erlang terms, like `{:ok, {:user, "name"}}`?
You can use nested patterns. The extraction patterns compose just like regular Gleam patterns. You would match it like this: erlang.Ok(erlang.Tuple2(erlang.Atom("user"), name)) -> .... This allows you to deconstruct deeply nested and complex data structures in one go.
Why do I need `erlang.unsafe_coerce` sometimes?
unsafe_coerce is a function that tells the Gleam compiler, "Trust me, I know this erlang.Term is actually of this more specific type." You use it after you've already confirmed the type using a guard function like erlang.is_atom. It's an escape hatch that is necessary because the compiler can't always infer the type from the guard function alone. Use it with caution and only after a proper type check.
Conclusion: The Key to a Polyglot BEAM Future
Erlang Extraction is more than just a syntax feature; it's a cornerstone of Gleam's philosophy of pragmatism and safety. It empowers developers to build with the confidence of a modern, statically-typed language without sacrificing the decades of innovation and robust libraries available on the BEAM.
By mastering how to safely unwrap and pattern match on external Erlang and Elixir terms, you unlock the full potential of Gleam as a citizen of the BEAM ecosystem. You can now confidently integrate Gleam into existing projects, call any library, and build bridges between systems, all while keeping the strong safety guarantees that make Gleam so compelling.
Disclaimer: The Gleam language and its standard library are under active development. While the concepts described here are stable, always refer to the official Gleam documentation for the latest syntax and API details corresponding to your Gleam version.
Published by Kodikra — Your trusted Gleam learning resource.
Post a Comment