A Simple Example of Calling an Elixir Library from Gleam
I’ve been experimenting a bit with Gleam and Elixir lately as part of my search for a new programming language.
One of Gleam’s flagship features is that it can call Elixir code and libraries, but I couldn’t find any examples of how to do that. I wrote a simple example of calling an Elixir library from a Gleam project, based on my beginner’s understanding of the Gleam/Elixir/Erlang ecosystem.
Install dependencies ๐︎
For this example, I’m using
- Gleam 1.10.0
- Erlang 27.3.4
- Elixir 1.18.3
You can install these dependencies however you want. I use Nix to manage my dependencies, so I install everything by creating a flake.nix as follows.
{
description = "Dev environment for gleam-call-elixir-simple";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
# 27.3.4
erlang-nixpkgs.url = "github:NixOS/nixpkgs/8406224e30c258025cb8b31704bdb977a8f1f0";
# 1.10.0
gleam-nixpkgs.url = "github:NixOS/nixpkgs/3866ad91cfc172f08a6839def503d8fc2923c603";
};
outputs = {
self,
flake-utils,
erlang-nixpkgs,
gleam-nixpkgs,
} @ inputs:
flake-utils.lib.eachDefaultSystem (system: let
erlang = erlang-nixpkgs.legacyPackages.${system}.beam27Packages.erlang;
elixir = erlang-nixpkgs.legacyPackages.${system}.beam27Packages.elixir;
gleam = gleam-nixpkgs.legacyPackages.${system}.gleam;
inotify-tools = erlang-nixpkgs.legacyPackages.${system}.inotify-tools;
in {
devShells.default =
erlang-nixpkgs.legacyPackages.${system}.mkShell
{
packages = [
erlang
elixir
gleam
inotify-tools
];
shellHook = ''
echo "erlang" $(erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell)
gleam --version
'';
};
formatter = erlang-nixpkgs.legacyPackages.${system}.alejandra;
});
}
If I run nix develop, I see that erlang and Gleam are available in my shell:
$ nix develop
erlang "27"
gleam 1.10.0
Create the project ๐︎
I create a project using gleam new. I’m hosting the project outside of Github, so I add --skip-github to skip the Github-specific files Gleam adds by default:
PROJECT_NAME='call_elixir'
gleam new --name "${PROJECT_NAME}" --skip-github .
Sidequest: Working around a Gleam package bug ๐︎
From here, I try to run the boilerplate code that gleam new generated for me, but it fails:
$ gleam run
Resolving versions
Downloading packages
Downloaded 2 packages in 0.01s
Compiling gleam_stdlib
error: Incompatible Gleam version
The package `gleeunit` requires a Gleam version satisfying 1.11.0 <= v but you are using v1.10.0.
The issue is that gleam new added gleeunit 1.4.0 as a dependency, but that package depends on Gleam 1.11.0, which is newer than my local version of Gleam (1.10.0).
Someone else ran into this same issue and filed a bug on Github just an hour before I hit this.
I can work around this by forcing Gleam to downgrade to gleeunit 1.3.1:
$ gleam add --dev gleeunit@1.3.1
Resolving versions
Downloading packages
Downloaded 1 package in 0.00s
Added gleeunit v1.3.1
And then gleam run and gleam test work as expected:
$ gleam run
Compiling gleeunit
Compiling call_elixir
Compiled in 0.28s
Running call_elixir.main
Hello from call_elixir!
$ gleam test
Compiled in 0.01s
Running call_elixir_test.main
.
Finished in 0.006 seconds
1 tests, 0 failures
Install an Elixir package ๐︎
I want to pick a package that has simple semantics, so how about a CSV library?
The hex package manager shows that the most popular CSV package is called CSV, so I install that:
$ gleam add csv
Resolving versions
Downloading packages
Downloaded 1 package in 0.00s
Added csv v3.2.2
Sidenote: There’s a Gleam-native CSV library called gsv, but I’m using an Elixir library instead so I can practice calling a non-Gleam library.
Testing the CSV package with Elixir ๐︎
First, I need to understand how to call the CSV package APIs at all, and then I can write an Elixir wrapper for the APIs I need.
I want to call the CSV.encode function, which has this signature:
@spec encode(Enumerable.t(), [encode_options()]) :: Enumerable.t()
So, encode has two parameters:
- An object that implements the
Enumerableprotocol. - (optional) An
encode_options()type.
And it returns an object that implements the Enumerable protocol.
I don’t know Elixir, so I start iex, the interactive Elixir shell, to understand the semantics:
iex
Then, I install the CSV package within iex:
iex> Mix.install([:csv])
Resolving Hex dependencies...
Resolution completed in 0.008s
New:
csv 3.2.2
* Getting csv (Hex package)
Now, I try one of the examples from the CSV package documentation:
iex> [~w(a b), ~w(c d)]
[["a", "b"], ["c", "d"]]
iex> |> CSV.encode
#Function<61.117496853/2 in Stream.transform/3>
iex> |> Enum.take(2)
["a,b\r\n", "c,d\r\n"]
I don’t understand Elixir’s sigil syntax yet, and I don’t like examples with “a” and “b”, so here’s a rewrite that feels more intuitive to me:
iex> CSV.encode([["movie", "rating"], ["The Godfather", 10], ["Gigli", 2]])
#Function<61.117496853/2 in Stream.transform/3>
iex> |> Enum.to_list()
["movie,rating\r\n", "The Godfather,10\r\n", "Gigli,2\r\n"]
iex> |> IO.puts()
movie,rating
The Godfather,10
Gigli,2
:ok
Okay, so it looks like CSV.encode takes in a list of list of strings and returns an Enumerable of strings.
Create a Gleam wrapper for the Elixir package ๐︎
Now that I understand the semantics of CSV.encode, I need to write a wrapper function to call it from Gleam.
The main challenge of writing a Gleam wrapper for an Elixir function is that the two languages don’t have matching types. Gleam uses more strict static typing, whereas Elixir uses more flexible dynamic typing.
Wrapping CSV.encode ๐︎
I want to call the CSV.encode function, whose signature, again, looks like this:
@spec encode(Enumerable.t(), [encode_options()]) :: Enumerable.t()
It doesn’t look like the Gleam standard library has any equivalent of Elixir’s Enumerable, so I need to use another Elixir API to convert from Enumerable to something Gleam understands.
Enum.to_list seems like the best option, as it returns an Elixir built-in list type, and Gleam has an equivalent List type.
So, first, I’ll define a Gleam wrapper function for the Elixir CSV.encode API:
// Create a custom Gleam type to represent the type we receive from Elixir.
type ElixirEnumerable
@external(erlang, "Elixir.CSV", "encode")
fn csv_encode(data: List(List(String))) -> ElixirEnumerable
The @external attribute allows me to call Elixir code from Gleam. Gleam, Elixir, and Erlang all can compile to bytecode that runs on the BEAM virtual machine. Within BEAM, the CSV.encode function appears under the namespace Elixir.CSV, so that’s why I need to specify Elixir in the @external attribute.
I define the input paramater as a Gleam list of list of strings (List(List(String))), which is compatible with Erlang’s Enumerable type.
CSV.encode returns an Elixir Enumerable, but I don’t know a Gleam equivalent to that, so I define a custom type of ElixirEnumerable.
Gleam code can’t do anything with ElixirEnumerable because it doesn’t have any data that Gleam knows how to access natively, so I need a way to convert ElixirEnumerable to a Gleam-native type.
Converting an Elixir Enumerable to a Gleam List ๐︎
To convert from Elixir’s Enumerable type to a Gleam List, I declare another external function for converting the result:
@external(erlang, "Elixir.Enum", "to_list")
fn enum_to_list(elixir_enum: ElixirEnumerable) -> List(String)
Here, I’m using Elixir’s Enum.to_list function to convert the ElixirEnumerable to an Elixir List type, which seems to match the Gleam List type.
Creating a Gleam-friendly wrapper ๐︎
Now that I’ve written functions that can wrap the Elixir APIs I want, it’s time to tie it all together with a function I’ll expose to my Gleam app from this module:
pub fn encode(data: List(List(String))) -> List(String) {
data
|> csv_encode
|> enum_to_list
}
The function is simple. It takes data in the form of a list of list of strings, then uses the Gleam pipe operator to pass it to csv_encode, then uses the pipe operator again to convert the result to a Gleam-compatible list of strings.
This is the only function in my csv module that I declare as public, as the other wrappers are too low-level to be useful to Gleam clients of this module.
The full module looks like this:
// Create a custom Gleam type to represent the type we receive from Elixir.
type ElixirEnumerable
@external(erlang, "Elixir.CSV", "encode")
fn csv_encode(data: List(List(String))) -> ElixirEnumerable
@external(erlang, "Elixir.Enum", "to_list")
fn enum_to_list(elixir_enum: ElixirEnumerable) -> List(String)
pub fn encode(data: List(List(String))) -> List(String) {
data
|> csv_encode
|> enum_to_list
}
Calling my wrapper function ๐︎
Now that I have a Gleam-native wrapper for the CSV.encode function, I can write a simple Gleam app to call my wrapper:
import gleam/io
import gleam/string
import csv
pub fn main() {
[
["Title", "Author", "Release Year"],
["Infinite Jest", "David Foster Wallace", "1996"],
["Emma", "Jane Austen", "1815"],
["Catch-22", "Joseph Heller", "1961"]
] |> csv.encode()
|> string.concat()
|> io.print()
}
I’m creating a simple list of list of strings, passing it to my csv.encode Gleam wrapper. It returns a list of strings, so I call Gleam’s string.concat standard library function to join strings into a single string. Finally, I call io.print to print the result to the console.
$ gleam run
Compiling call_elixir
Compiled in 0.20s
Running call_elixir.main
Title,Author,Release Year
Infinite Jest,David Foster Wallace,1996
Emma,Jane Austen,1815
Catch-22,Joseph Heller,1961
It works! I successfully called the Elixir CSV library from my simple Gleam application.
Source code ๐︎
The full source of this example is available below:
–
Thanks to Louis Pilford and DisguisedPigeon for their helpful feedback on this post.
Read My Book

I'm writing a book of simple techniques to help developers improve their writing.
My book will teach you how to:
- Create clear and pleasant software tutorials
- Attract readers and customers through blogging
- Write effective emails
- Minimize pain in writing design documents
Be the first to know when I post cool stuff
Subscribe to get my latest posts by email.