← Back to listing

Static types on BEAM: Gleam

Development Gleam Erlang B e a m

While Elixir is a strongly typed language, the most common complaint I’ve encountered regarding Elixir is the lack of static typing. The Elixir core team has investigated the feasibility of introducing a backwards-compatible way of introducing a static type system in the past, but ultimately concluded that Elixir won’t be getting one (at least not via official means).

Thankfully, Elixir provides many tools for addressing typical type-related issues. The strong typing means implicit type casting is out of the picture, and you can avoid many other pitfalls of dynamic typing with a thorough use of guards, type specs and pattern matching. With these tools, you can be fairly certain that the value you are operating with is what you think it is. This approach, however, places the burden on the developer.

What if you really need static types in your Elixir project?

Enter Gleam

Gleam is a fairly new language that runs on the BEAM. While still compiling down to Erlang, it is statically typed and offers full type inference. This means that you will be able to catch any type errors in your Gleam code at compile time, and you don’t need to annotate your code if you don’t want to. I do though, since it adds lots of helpful information to my future self :).

However, you probably don’t want to rewrite your entire existing Elixir project in Gleam just for static typing. What excites me in such a scenario is the fact that you can call Gleam code directly from your Elixir code with zero overhead, since Gleam compiles down to Erlang, and Erlang code can already be called from Elixir as-is. This means that you can write the critical and difficult parts of your code in Gleam, while keeping the overall system use Elixir.

A pattern I’ve recently investigated is to write as much of the core of your software in Gleam as possible, while keeping the OTP systems and interface around your core in Elixir. An advantage I see in this is that I’m able to use all the know-how, dependencies and tooling from Elixir to setup my project, while using Gleam to be certain that the core functionality of my system is statically typed. I feel like this approach also makes sense when you consider that things like external inputs need to be treated as unknown types anyway, so it’s natural to take them in via a dynamically typed language and then convert them to known types your core expects.

This approach does not really differ from how you would write Elixir in most cases anyway. For example, if you work on an Elixir API, you probably already convert the parameters passed to your Phoenix controllers into more well-defined types before calling your context modules with the values. The thing that changes is the fact that once you cross that boundary, you can benefit from static typing, giving you extra guarantees about the correctness of your code (and relieving you from typing many @spec definitions!).

However, it is worth understanding the limitations of this approach: the Elixir side cannot know if you are calling Gleam functions with bad values, so the compiler won’t be able to stop you from making a call that isn’t known to be type-safe. Thankfully, given that Gleam compiles down to Erlang, you will be able to still benefit from tools like Dialyzer, which can still point out that your calls are not going to work out. This means you still get to experience the features that Erlang/Elixir offer for addressing type concerns, even in such a “worst case scenario”.

How to use Gleam (in an Elixir project)

Installation of Gleam can be done in many ways, including via asdf. This is my personally preferred method, but in case you’re looking for alternatives, check out the Getting Started section of the official Gleam site.

As for using Gleam in an Elixir project, use the official mix plugin and follow its README. Once that’s done, just place your Gleam modules into the src directory. Now you’re ready to use Gleam through Elixir!

Because Gleam compiles down to Erlang, you can simply call the compiled Gleam functions as you would any Erlang function. Given a hello_world module in src/hello_world.gleam, you can call it via :hello_gleam.hello(). If you want a more Elixir-like experience, you can even alias the modules (alias :hello_gleam, as: HelloGleam)!

First impressions

I’ve spent a little while now working on such a setup, and the first impressions have been very positive. A particular highlight I wish to share is the Gleam error messages. This might be bread and butter for anyone working with languages like TypeScript or Rust, but for someone who primarily works with a dynamic language such as Elixir, being able to see things like these in my terminal brings a lot of joy:

Compiling hello_world

error: Type mismatch
  ┌─ /blog_demo/src/hello_world.gleam:7:3
  │
7 │   "Hello, from gleam!"
  │   ^^^^^^^^^^^^^^^^^^^^

The type of this returned value doesn't match 
the return type annotation of this function.

Expected type:

    Int

Found type:

    String

With all this, I’m looking forward to seeing where Gleam heads next, and what things I will be able to build with it :)