Update: This was very much an initial exploration. There is now an official project with a mix_gleam plugin. Please check that out!
At Contact Stack, we've chosen Elixir & Phoenix as the core of our tech stack and we're happy with the choice. But... we do miss having a really good type system and a helpful compiler (like with Elm) to guide us through development.
Elixir targets the BEAM virtual machine and there are few statically typed languages that target the BEAM as well. Here we're going to look into a simple set up for integrating Gleam into your Elixir project using a Mix Compiler Task.
Gleam is a statically typed functional programming language which uses a syntax that is more approachable to, say, Javascript, C or Java programmers than some ML flavoured languages. The Gleam compiler is written in Rust and outputs Erlang source files which can then be compiled to BEAM by the Erlang compiler.
Mix is a multi-functional build tool for Elixir projects. It can manage dependencies, run tests, execute tasks and compile your Elixir code. It also supports compiling erlang files by default and can be extended with Mix Compiler Tasks to compile other languages. We're going to create a Mix Compiler Task for Gleam so that Mix can use the Gleam compiler to build Gleam files.
You can try the steps below yourself and check out the demonstration repository: https://github.com/michaeljones/gleam-phoenix-mix.
Here we go!
We're assuming you have the gleam
compiler available in your environment to execute. Run this to check.
gleam --version
If you don't, then you can follow the installation instructions over in the Gleam docs.
Now we're going to go through setting up a Phoenix project from scratch. We won't go into the details but just so we have a clear base to work with.
Run the following commands to set up an empty phoenix project:
mkdir gleam-phoenix-mix
cd gleam-phoenix-mix
mix phx.new . --app my_app
mix ecto.create
cd assets
npm install
cd ..
Now we need to create our mix compiler task. We put this in a lib/mix/tasks/compile
directory to reflect the module name that we need to give it which is Mix.Tasks.Compile.Gleam
. So we create our new directory:
mkdir -p lib/mix/tasks/compile/
And add the follow code in a file called lib/mix/tasks/compile/gleam.ex
.
defmodule Mix.Tasks.Compile.Gleam do
use Mix.Task.Compiler
def run(_args) do
System.cmd("gleam", ["build"])
:ok
end
end
All this is really doing is running the gleam compiler directly with the 'build' argument to get it to build all the gleam files it can find.
Run the elixir compiler so that we compile that mix task before we try to use it.
mix compile.elixir
Now to make gleam happy we need to set up a .toml
config file for it. So we create a file called gleam.toml
in the root of the project with the follow content.
name = "my_app"
Then we create a src
directory for the gleam files. The Gleam compiler expects the gleam files to be in a src
directory. This is different to elixir/mix which expects them in the lib
directory by default. Elixir/mix can be configured but Gleam can't at the time of writing.
mkdir src
We then create our Gleam module by making a file called src/hello_world.gleam
with the following code in it:
pub fn hello() {
"Hello, from gleam!"
}
Next we make the following change to the mix.exs
file to add our compiler task to the list of compilers being run when you do mix compile
and to make sure mix's erlang compiler looks in the gen
folder
for erlang files. The gleam
compiler compiles .gleam
files from the src
folder into .erl
files in the gen
folder so we need the erlang compiler to find them there.
elixir: "~> 1.5",
elixirc_paths: elixirc_paths(Mix.env()),
+ erlc_paths: ["src", "gen"],
- compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+ compilers: [:phoenix, :gettext, :gleam] ++ Mix.compilers(),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
I assume that Mix runs the compilers from the start to the end of the list. So we want the :gleam
entry to be before the :erlang
entry that's in Mix.compilers()
so that the Erlang compiler will run after the Gleam compile and compile the Erlang files that the Gleam compiler creates.
As a simple demonstration of interop between Elixir & the compiled gleam files, make the following change to lib/my_app_web/controllers/page_controller.ex
:
defmodule MyAppWeb.PageController do
use MyAppWeb, :controller
def index(conn, _params) do
- render(conn, "index.html")
+ render(conn, "index.html", title: :hello_world.hello())
end
end
This works because Gleam modules are compiled to Erlang modules and, in Elixir, Erlang modules are accessible via the atom that is their name.
And then we make the following change to lib/my_app_web/templates/page/index.html.eex
:
<section class="phx-hero">
- <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
+ <h1><%= @title %></h1>
<p>A productive web framework that<br/>does not compromise speed or maintainability.</p>
</section>
Finally we can run:
mix compile
mix phx.server
And load http://localhost:4000
in our browser to see "Hello, from gleam!" in the centre of the standard Phoenix welcome page.
Remember, if you're curious you can check out the final result at this demonstration repo on Github: https://github.com/michaeljones/gleam-phoenix-mix
Final Thoughts
It is exciting to see that it isn't too hard to incorporate Gleam code into an Elixir project using Mix. It is great that Mix is built with this kind of extensibility in mind. There are definitely more questions to answer and rough edges to smooth out but it is exciting to have an avenue to using a strongly typed language on the BEAM.
Top comments (14)
Nice article. This has allowed me to add a bit of gleam to my existing Elixir project.
I found that if I want to use gleam libraries I need to put them in a
rebar.config
in the project root so that the project compiles, but then also add them to themix.exs
so that they are available at run time. Do you have any ideas for avoiding this duplication?Thanks for the comment. I'm sorry to say that I've not gone much further on this so I actually can't help you with your issue. I'm excited by Gleam but I'm still trying to figure out how to fit it into my projects.
I asked on the Elixir Forum Gleam thread about further interop between Phoenix, Ecto & Gleam but lpil, the Gleam creator, suggests that this might be quite hard due to Elixir macros. I'm keen to explore more but I haven't found the time.
OK - thanks for the reply! I'll let you know if I find out more myself.
I contributed a fix to Gleam that means it now finds gleam source for dependencies in a mix project github.com/gleam-lang/gleam/pull/596
Also instead of defining your own compiler module an alias will do
aliases: ["compile.gleam": "cmd gleam build"]
I put a working example here github.com/midas-framework/elixir_...
Great! Works perfectly. Thanks Peter!
There is now. github.com/gleam-lang/mix_gleam
Which should work even better.
I've also updated the elixir with gleam example
Nice writeup! Would you know if it is possible to register Gleam modules to be accessed using the same naming convention of Elixir? I know that at the end of the day, all module names are just atoms, but mixing the style will make the code base inconsistent, IMO.
Thanks for the question. I don't know too much about the situation, I'm afraid.
I think Gleam modules are compiled to be maximally accessible from Erlang. It has crossed my mind that Gleam could have an output mode that namespaced things for Elixir (I think Elixir modules are just atoms that start with "Elixir." though I'm far from an expert.) I don't think that is planned at the moment though. Might be worth raising. The gleam community has a uses GitHub discussions (github.com/gleam-lang/gleam/discus...) and Discord (discord.com/invite/Fm8Pwmy) for communicating mostly at the moment.
I imagine that if you were to create a library in Gleam and wanted to make it even friendlier for Elixir programmers then you publish some Elixir modules into the bundle that act like a shim to provide Elixir style access to the functions and types.
Posted on: reddit.com/r/gleamlang/
huh, turns out it's pretty easy to add compilers to Mix.
But I guess the generated erlang files would be in the same directory with this setup.. a proper integration would hide them in
_build
Yeah, not that hard at all, eh? :)
Yeah, the Gleam compiler would have to be tweaked so it was at least configurable to target the _build folder instead. The creator of Gleam is interested in that route but is unsure of how best to approach it: github.com/gleam-lang/gleam/issues...
Good one! I like Gleam and watching it since a while. Now I have a good reason to use it :)
Did you use Gleam for some specific problem that would be more difficult to solve using just Elixir?
Thanks for commenting. Honestly, no. I haven't written much in Gleam at all. Given that it targets the BEAM I imagine it will one day be good for similar problems as Elixir. At the moment it is quite young and I struggle to figure out how best to use it. My main interest is that I really enjoy working with Elm as a simple, clear language with a strong static type system and a friendly compiler and to the degree that Gleam has similar goals but targets the Erlang VM I would like to see it succeed.