Published 18 Nov, 2024 by Louis Pilfold
Gleam is a type-safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.6.0 has been published, featuring so many excellent improvements that I struggled to title this post. Let’s take a look at them now, but first, the Gleam developer survey!
This is your chance to shape our 2025 plans, so fill it in and share with your friends! Any level of Gleam experience is OK (or even none yet at all), so please fill it in so we can use your feedback to decide what to focus on in the coming year.
Right. Now on to the release changes.
Context aware errors
A big part of what makes Gleam productive is the way its powerful static analysis can immediately provide you with feedback as you type, enabling you to confidently make changes within large or unfamiliar codebases. Much of this feedback will come in the form of error messages, so it is vital that they are as clear and as understandable as possible.
With this release Gleam’s errors are now context aware. Using data from the compiler’s code analysis system type errors now use the names and syntax that the programmer would use within that specific area of the code.
For example, here is some code with a type error.
import gleam/order
pub fn run(value: order.Order) -> Int {
100 + value
}
error: Type mismatch
┌─ /src/problem.gleam:4:9
│
4 │ 100 + value
│ ^^^^^
The + operator expects arguments of this type:
Int
But this argument has this type:
order.Order
Notice how the Order
type is qualified with the module name, the same as the
programmer would write in this module. If the module is aliased when imported
then that alias will also be used in the error.
-import gleam/order
+import gleam/order as some_imported_module
-pub fn run(value: order.Order) -> Int {
+pub fn run(value: some_imported_module.Order) -> Int {
100 + value
}
error: Type mismatch
┌─ /src/problem.gleam:4:9
│
4 │ 100 + value
│ ^^^^^
The + operator expects arguments of this type:
Int
But this argument has this type:
- order.Order
+ some_imported_module.Order
Or the type could be imported in an unqualified fashion, in which case the error would not have the redundant qualifier.
-import gleam/order
+import gleam/order.{type Order}
-pub fn run(value: order.Order) -> Int {
+pub fn run(value: Order) -> Int {
100 + value
}
error: Type mismatch
┌─ /src/problem.gleam:4:9
│
4 │ 100 + value
│ ^^^^^
The + operator expects arguments of this type:
Int
But this argument has this type:
- order.Order
+ Order
You could even define your own types that hide prelude types like Int
,
Float
, and String
, at which point the prelude types would be displayed with
a qualifier.
Perhaps don’t write code like this though, your coworkers probably won’t be happy with you if you do.
pub type Int
pub type String
pub fn run() {
[100, "123"]
}
error: Type mismatch
┌─ /src/thingy.gleam:6:9
│
6 │ [100, "123"]
│ ^^^^^
All elements of a list must be the same type, but this one doesn't
match the one before it.
Expected type:
gleam.Int
Found type:
gleam.String
These context-aware errors should reduce the mental overhead of understanding them, making Gleam programming easier and more productive. Thank you Surya Rose for this!
Context aware editing hovering
Not being satisfied with improving only the error messages, Surya has also made the language server hovering context aware. This means that if you hover over Gleam code in your editor the type information will be shown using the appropriate names and syntax for that module.
import gleam/option
const value = option.Some(1)
// ^ hovering here shows `option.Option(Int)`
import gleam/option.{type Option as Maybe}
const value = option.Some(1)
// ^ hovering here shows `Maybe(Int)`
Thank you again Surya!
Add annotations code action
In Gleam all type annotations are optional, full analysis is always performed. Adding annotations does not make your code more safe or well typed, but we still think it’s a good idea to add them to make your code easier to read.
If your colleague has sadly forgotten this and not written any annotations for their functions, you can use the language server’s new code action within your editor to add missing annotations. Place your cursor within this function that is missing annotations:
pub fn add_int_to_float(a, b) {
a +. int.to_float(b)
}
And after triggering the code action this code becomes:
pub fn add_int_to_float(a: Float, b: Int) -> Float {
a +. int.to_float(b)
}
Thanks to, you guessed it, Surya Rose for this new feature!
Erlang compilation daemon
When targeting the Erlang virtual machine the build tool makes use of the Erlang compiler to generate BEAM bytecode, taking advantage of all of its optimisations. The Erlang compiler is written in Erlang, so this would involve booting the virtual machine and the compiler once per dependency. On a slow machine this could take as much as half a second each time, which would add up and slow down from-scratch build times.
The build tool now boots one instance of the virtual machine and sends code to it for compilation when needed, completely removing this cost. This change will be most impactful for clean builds such when changing Gleam version or in your CI pipeline, or in monorepos of many packages.
Thank you yoshi for this!
Variant inference
The compiler now infers and keeps track of the variant of custom types within expressions that construct or pattern match on them. Using this information it can now be more precise with exhaustiveness checking, field access, and record updates.
That’s not the clearest explanation, so here’s some examples. Imagine a custom
type named Pet
which has two variants, Dog
and Turtle
.
pub type Pet {
Dog(name: String, cuteness: Int)
Turtle(name: String, speed: Int, times_renamed: Int)
}
Any place where a value of type Pet
is used, the code would have to take into
account both variants, even if it seems obvious to us as humans that the value
is definitely a specific variant at this point in the code. The compiler would
still require that you take the other variants into account.
With variant inference you now no longer need to include patterns for the other variants.
pub fn main() {
// We know `charlie` is a `Dog`...
let charlie = Dog("Charles", 1000)
// ...so you do not need to match on the `Turtle` variant
case charlie {
Dog(..) -> todo
}
}
It also works for the record update syntax. This code would previously fail to
compile due to the compiler not being able to tell that pet
is the right
variant.
pub fn rename(pet: Pet, to name: String) -> Pet {
case pet {
Dog(..) -> Dog(..pet, name:)
Turtle(..) -> Turtle(..pet, name:, times_renamed: pet.times_renamed + 1)
}
}
It also works for the field accessor syntax, enabling their use with fields that do not exist on all of the variants.
pub fn speed(pet: Pet) -> Int {
case pet {
Dog(..) -> 500
// Here the speed field can be safely accessed even though
// it only exists on the `Turtle` variant.
Turtle(..) -> pet.speed
}
}
Thank you Surya Rose, the superstar of this release!
Precise dependency updates
The gleam update
command can be used to update your dependencies to the latest
versions compatible with the requirements specified in gleam.toml
.
You can now also use this command to update a specific set of dependencies,
rather than all of them. If I wanted to update lustre
and gleam_json
I could
run this command:
gleam update lustre gleam_json
Thank you Jason Sipula for this feature!
Monorepo documentation links
When a package is published Gleam will also generate and upload HTML documentation for users to read. This documentation includes links to the source code in its repository, assuming it is hosted using a known service such as Forgejo or GitHub.
Unfortunately these links would not be accurate if you were using a monorepo or if the package was not located at the root of the repository for some other reason.
To resolve this the repository
config in gleam.toml
can now optionally
include a path
so Gleam knows how to build the correct URLs.
[repository]
type = "github"
user = "pink-inc"
repo = "monorepo"
path = "packages/fancy_package"
Thank you Richard Viney!
Result handling hints
Errors are represented as values in Gleam, and exceptions are rare and not used
for flow control. This means that Gleam programmers will very often be using the
Result
type in their programs, which can be initially confusing to programmers
more familiar with exception based error handling.
One common stumbling block is how to use a value that is wrapped in a Result
.
To help with this the compiler can now suggest to pattern match on these result
values when appropriate.
error: Type mismatch
┌─ /src/one/two.gleam:6:9
│
6 │ int.add(1, not_a_number)
│ ^^^^^^^^^^^^
Expected type:
Int
Found type:
Result(Int, a)
Hint: If you want to get a `Int` out of a `Result(Int, a)` you can pattern
match on it:
case result {
Ok(value) -> todo
Error(reason) -> todo
}
Thank you Giacomo Cavalieri!
Optional dependencies are now optional
Gleam uses Hex, the package manager for the BEAM ecosystem. It has a concept of optional packages, allowing a package to specify a version constraint on another package without causing it to be added to the dependency graph. This constraint is only used if a third package also depends on that package. This isn’t very useful in Gleam, but Elixir commonly makes use of it via its compile time metaprogramming system.
Until now the Gleam build tool would treat these optional dependencies as regular dependencies, always downloading them. With this release it now handles them correctly and will ignore packages where all constraints are marked as optional. This will reduce compile times for some Gleam projects that depend on Elixir packages.
Thank you Gustavo Inacio!
Optimised bit arrays
Gleam inherits the bit-syntax from Erlang, which is a literal syntax for constructing and parsing binary data. It can be a very nice alternative to bitwise operators commonly used in other language, and makes Gleam a pleasant language for working with binary data.
Performance wizard Richard Viney has been hard at work optimising the implementation on JavaScript, and has made their creation between 2 and 40 times faster, depending on the input data. Thank you Richard! Very impressive work!
Unsafe number warnings
When compiling to JavaScript Gleam uses JavaScript’s number types, to ensure good interop with JavaScript code and the JavaScript platform as a whole. This means that it inherits some shortcomings that do not exist on the BEAM, namely a maximum and minimum safe number size.
When targeting JavaScript the compiler now emits a warning for integer literals and constants that lie outside JavaScript’s safe integer range, letting the programmer know that their code may have unexpected behaviour due to lost precision.
warning: Int is outside the safe range on JavaScript
┌─ /Users/richard/Desktop/int_test/src/int_test.gleam:1:15
│
1 │ pub const i = 9_007_199_254_740_992
│ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer on JavaScript
This integer value is too large to be represented accurately by
JavaScript's number type. To avoid this warning integer values must be in
the range -(2^53 - 1) - (2^53 - 1).
See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
properties for more information.
Thank you again Richard Viney!
(un)qualification code actions
When using types and values from other Gleam modules the programmer has the choice as to whether to use them in a qualified or an unqualified fashion.
import gleam/option.{type Option}
pub fn item() -> Option(Int) { // <- unqualified
option.Some(9) // <- qualified
}
Most commonly Gleam programmers will choose to qualify all functions, and to unqualify types that share the same name as their containing module.
Changing from one to the other is trivial, but also boring and time-consuming for types and values that are used many times in a module. The language server now has two code actions to automate this annoyance away!
Within your editor place your cursor on an instance of the type or value you wish to change, select the “qualify” or “unqualify” code action, and the language server will instantly update all the instances of that type or value in the module along with the required import statement.
Thank you Jiangda Wang for this code action!
JavaScript project creation
Gleam can compile to JavaScript as well as to Erlang. If you know your project
is going to target JavaScript primarily you can now use gleam new myapp
--template javascript
to create a new project that is already configured for
JavaScript, saving you from adding the target = "javascript"
to your gleam.toml
.
Thank you Mohammed Khouni for this!
And the rest
And thank you to the bug fixers and error message improvers Antonio Iaccarino, Giacomo Cavalieri, Jiangda Wang, Markus Pettersson, PgBiel, Richard Viney, Surya Rose, yoshi, and Zak Farmer!
For full details of the many fixes and improvements they’ve implemented see the changelog.
A call for support
Gleam is not owned by a corporation; instead it is entirely supported by sponsors, most of which contribute between $5 and $20 USD per month. I currently earn less than the median salary tech lead salary for London UK, the city in which I live, and Gleam is my sole source of income.
If you appreciate Gleam, please support the project on GitHub Sponsors with any amount you comfortably can. I am endlessly grateful for your support, thank you so much.
- 00bpa
- Aaron Gunderson
- Abdulrhman Alkhodiry
- ad-ops
- Adam Brodzinski
- Adam Johnston
- Adam Wyłuda
- Adi Iyengar
- Adi Salimgereyev
- Adrian Mouat
- Ajit Krishna
- Aleksei Gurianov
- Alembic
- Alex
- Alex Houseago
- Alex Manning
- Alex Viscreanu
- Alexander Koutmos
- Alexander Stensrud
- Alexandre Del Vecchio
- Ameen Radwan
- Andrea Bueide
- AndreHogberg
- andrew
- Andrii Shupta
- Andris Horváth
- Antharuu
- Anthony Khong
- Anthony Maxwell
- Anthony Scotti
- Antonio
- Arthur Weagel
- Arya Irani
- Azure Flash
- Barry Moore
- Bartek Górny
- Ben Martin
- Ben Marx
- Ben Myles
- Benjamin Kane
- Benjamin Peinhardt
- Benjamin Thomas
- Berkan Teber
- bgw
- Bill Nunney
- Bjarte Aarmo Lund
- BlockListed
- Brad Mehder
- brettkolodny
- Brian Dawn
- Brian Glusman
- Bruno Michel
- bucsi
- Cameron Presley
- Carlo Gilmar
- Carlo Munguia
- Carlos Saltos
- Carlos Silva
- Chad Selph
- Charlie Duong
- Charlie Govea
- Chaz Watkins
- Chew Choon Keat
- Chris Donnelly
- Chris Haynes
- Chris King
- Chris Lloyd
- Chris Ohk
- Chris Rybicki
- Christopher David Shirk
- Christopher De Vries
- Christopher Dieringer
- Christopher Jung
- Christopher Keele
- CJ Salem
- clangley
- Claudio
- CodeCrafters
- Coder
- Cole Lawrence
- Colin
- Comamoca
- Constantin (Cleo) Winkler
- Cristiano Carvalho
- Daigo Shitara
- Damir Vandic
- Dan
- Dan Dresselhaus
- Danielle Maywood
- Danny Arnold
- Danny Martini
- Dave Lucia
- David Bernheisel
- David Cornu
- David Sancho
- Dennis Dang
- dennistruemper
- Dillon Mulroy
- Dima Utkin
- Dimos Michailidis
- Dmitry Poroh
- Donnie Flood
- Drew Olson
- ds2600
- ducdetronquito
- Dylan Carlson
- Edon Gashi
- eeeli24
- Eileen Noonan
- eli
- Emma
- EMR Technical Solutions
- Endo Shogo
- Eric Koslow
- Erik Terpstra
- erikareads
- ErikML
- Ernesto Malave
- Ethan Olpin
- Evaldo Bratti
- Evan Johnson
- evanasse
- Fabrizio Damicelli
- Fede Esteban
- Felix Mayer
- Fernando Farias
- Filip Figiel
- Florian Kraft
- Francis Hamel
- frankwang
- G-J van Rooyen
- Gabriel Vincent
- Gears
- Geir Arne Hjelle
- Georg H. Ekeberg
- George
- ggobbe
- Giacomo Cavalieri
- Giovanni Kock Bonetti
- Graeme Coupar
- grotto
- Guilherme de Maio
- Guillaume Heu
- Guillaume Hivert
- Gustavo Inacio
- Hamir Mahal
- Hammad Javed
- Hannes Nevalainen
- Hannes Schnaitter
- Hans Fjällemark
- Hans Raaf
- Hayes Hundman
- Hayleigh Thompson
- Hazel Bachrach
- Henning Dahlheim
- Henry Firth
- Henry Warren
- Heyang Zhou
- human154
- Humberto Piaia
- Iain H
- Ian González
- Ian M. Jones
- Igor Montagner
- Igor Rumiha
- ILLIA NEGOVORA
- Ingrid
- inoas
- Isaac
- Isaac Harris-Holt
- Isaac McQueen
- Ismael Abreu
- Ivar Vong
- J. Rinaldi
- Jacob Lamb
- Jake Cleary
- James Birtles
- James MacAulay
- Jan Skriver Sørensen
- Jason Sipula
- Javien Lee
- Jean-Luc Geering
- Jen Stehlik
- Jenkin Schibel
- jiangplus
- Jimpjorps™
- Joey Kilpatrick
- Joey Trapp
- Johan Strand
- John Björk
- John Gallagher
- John Pavlick
- John Strunk
- Jojor
- Jon Lambert
- Jonas E. P
- Jonas Hedman Engström
- Jonathan Arnett
- jooaf
- Joseph Lozano
- Joseph T. Lyons
- Joshua Steele
- Julian Lukwata
- Julian Schurhammer
- Justin Lubin
- Karl
- Kemp Brinson
- Kero van Gelder
- Kevin Schweikert
- kodumbeats
- Kramer Hampton
- krautkai
- Kritsada Sunthornwutthikrai
- Kryštof Řezáč
- Krzysztof G.
- Laurent Arnoud
- Leandro Ostera
- Lee Jarvis
- Leon Qadirie
- Leonardo Donelli
- lidashuang
- LighghtEeloo
- Lily Rose
- Loïc Tosser
- Lucas Pellegrinelli
- Lukas Bjarre
- Lukas Meihsner
- Luke Amdor
- Luna
- Manuel Rubio
- Marco A L Barbosa
- Marcus André
- Marcøs
- Mariano Uvalle
- Marius Kalvø
- Mark Holmes
- Mark Markaryan
- Markus Pettersson
- Markéta Lisová
- Marshall Bowers
- Martin Janiczek
- Martin Rechsteiner
- martonkaufmann
- Matt Champagne
- Matt Heise
- Matt Mullenweg
- Matt Robinson
- Matt Savoia
- Matt Van Horn
- Matthew Whitworth
- Max McDonnell
- max-tern
- metame
- Michael Duffy
- Michael Jones
- Michael Mazurczak
- Mikael Karlsson
- Mike
- Mike Nyola
- Mike Roach
- Mikey J
- Mikko Ahlroth
- MoeDev
- Moritz Böhme
- mpatajac
- MzRyuKa
- n8n - Workflow Automation
- Natanael Sirqueira
- Nathaniel Knight
- Nayuki
- NFIBrokerage
- Nick Chapman
- Nick Reynolds
- Nicklas Sindlev Andersen
- NicoVIII
- Niket Shah
- Ninaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- NineFX
- noam sauer-utley
- Nomio
- Norbert Szydlik
- Ocean
- OldhamMade
- Oliver Medhurst
- Oliver Tosky
- optizio
- Osman Cea
- PastMoments
- Patrick Wheeler
- Paul Gideon Dann
- Paul Guse
- Pawel Biernacki
- Pedro Correa
- Pete Jodo
- Peter Rice
- Peter Saxton
- PgBiel
- Philip Giuliani
- Philpax
- Pierrot
- Piotr Szlachciak
- Qdentity
- qingliangcn
- Race Williams
- Rasmus
- Ray
- Raúl Chouza
- re.natillas
- Rebecca Valentine
- Redmar Kerkhoff
- Reilly Tucker Siemens
- Renato Massaro
- Renovator
- Richard Viney
- Rico Leuthold
- Ripta Pasay
- Rob
- Robert Attard
- Robert Ellen
- Robert Malko
- Rodrigo Álvarez
- Ronan Harris
- Rotabull
- Rupus Reinefjord
- Ruslan Ustitc
- Ryan Moore
- Sam Aaron
- Sam Zanca
- sambit
- Sami Fouad
- Sammy Isseyegh
- sanabel-al-firdaws
- Santi Lertsumran
- Savva
- Saša Jurić
- Scott Trinh
- Scott Wey
- Sean Jensen-Grey
- Sean Roberts
- Sebastian Porto
- sekun
- Seve Salazar
- Shane Poppleton
- Shuqian Hon
- Simone Vittori
- star-szr
- Stefan
- Stephen Belanger
- Steve Powers
- Steve Toro
- Strandinator
- Sunil Pai
- syhner
- Sławomir Ehlert
- Tar-Tarus
- Theo Harris
- TheShmill
- Thomas
- Thomas Coopman
- Thomas Ernst
- Tim Brown
- Timo Sulg
- Tom Calloway
- Tom Schuster
- Tomasz Kowal
- tommaisey
- Tristan de Cacqueray
- Tristan Sloughter
- upsidedowncake
- Valerio Viperino
- Vic Valenzuela
- Victor Rodrigues
- Viv Verner
- Volker Rabe
- Weizheng Liu
- Wesley Moore
- Willyboar
- Wilson Silva
- Xucong Zhan
- Yamen Sader
- Yasuo Higano
- yoshi~
- Zak Farmer
- Zsombor Gasparin
- ~1847917
- Éber Freitas Dias
Thanks for reading, I hope you have fun with Gleam! 💜