Using Gleam Packages in Your Elixir Project
Posted on .
Using Gleam packages in an Elixir project is not as simple as adding {:lib, "1.2.3"}
to your
mix.exs
, but it's pretty close. There are two issues preventing this simple usage:
- Elixir's Mix doesn't know how to compile a Gleam package, which does not have any build setup. It's literally a bunch of Erlang files in a folder. I'm not sure if there is an issue about this.
- Gleam writes dev dependencies into the built
app.src
file, which causes the app to crash on startup, as the dev dependencies aren't there. Gleam issue #3035 deals with this matter.
To circumvent these issues, add your Gleam dependency to mix.exs
like this:
{:scriptorium, "~> 2.0.0", app: false, manager: :rebar3}
The first option prevents reading the app file of the dependency entirely. It seems to work for libraries, but I'm not sure of its effects on Gleam projects with startable apps. I believe they won't be automatically started at least. Most Gleam projects at this point are libraries or are explicitly started, so this is likely not an issue for you.
The second tells Mix to use Rebar to build the package. This seems to work even for a plain "Erlang files in a folder" type package that Gleam generates.
You can read more about these options in Mix's documentation.
Note that this only works for packages available in Hex, not for path dependencies.
Once you've gotten your dependency installed, remember that the calling syntax is different from a
typical Elixir module. Gleam modules are named according to their path with @
as a separator. For
example, to call parse_header
from scriptorium/parser/common
, use the following:
:scriptorium@parser@common.parse_header("...")
Also note that Gleam custom types map to tuples in Elixir, and more specifically records. In fact you can define a record on the Elixir side to match your Gleam types and operate on them. For example, here's a record from one of my early mixed Elixir/Gleam projects, GeoTherminator:
defmodule GeoTherminator.PumpAPI.Device do
require Record
Record.defrecord(:record, :device, [
:id,
:device_id,
:is_online,
:last_online,
:created_when,
:mac_address,
:name,
:model,
:retailer_access
])
# Create idiomatic Elixir type as an alias for Gleam side type
@type t :: :pump_api@device.device()
end
Now we can use GeoTherminator.PumpAPI.Device.t()
in typespecs, and calls like
GeoTherminator.PumpAPI.Device.record(my_device, :id)
and
GeoTherminator.PumpAPI.Device.record(my_device, name: "My Device")
to operate on the type in Elixir
land.