Published 19 Sep, 2024 by Louis Pilfold
Gleam is a type-safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.5.0 has been published, featuring lots of really nice developer experience and productivity improvements, including several useful language server code actions. Let’s take a look.
Before we start I just want to give extra thanks to Lambda, Gleam’s new primary sponsor. Gleam is an entirely open-source community driven project rather than being owned by any particular corporation or academic institution. All funding comes from sponsors, both corporate sponsors such as Lambda, and individuals sponsoring a few dollars a month on GitHub Sponsors. Thank you for making Gleam possible.
Context aware exhaustiveness errors and code action
The compile time error messages for inexhaustive pattern matching have been upgraded to show the unmatched values using the syntax the programmer would use in their code, respecting the aliases and imports in that module. For example, if you had this code:
import gleam/option
pub fn main() {
let an_option = option.Some("wibble!")
case an_option {
option.None -> "missing"
}
}
The error message would show the qualified option.Some(_)
as the missing
pattern:
error: Inexhaustive patterns
┌─ /root/prova/src/prova.gleam:5:3
│
5 │ ╭ case an_option {
6 │ │ option.None -> "missing"
7 │ │ }
│ ╰───^
This case expression does not have a pattern for all possible values. If it
is run on one of the values without a pattern then it will crash.
The missing patterns are:
option.Some(_)
This makes it easier to understand the error message, and the missing patterns can be copied from the error directly into the source code.
Further still, when there is one of these errors in the code the language server offers a code action to add the missing patterns for you.
// Before
pub fn run(a: Bool) -> Nil {
case a {}
}
// After
pub fn run(a: Bool) -> Nil {
case a {
False -> todo
True -> todo
}
}
Thank you Surya Rose for this! The code action is a real favourite of mine! I have been using it constantly.
Silent compilation
When you run a command like gleam run
or gleam test
it prints some progress
information.
$ gleam run
Compiling thoas
Compiling gleam_json
Compiling app
Compiled in 1.67s
Running app_test.main
Hello, world!
This is generally nice, but sometimes you only want to see the output from your
tests or your program, so a --no-print-progress
flag has been added to silence
this additional output.
$ gleam run --no-print-progress
Hello, world!
Additionally, this information is now printed to standard error rather than standard out, making it possible to redirect it elsewhere in your command line shell.
Thank you Ankit Goel and Victor Kobinski for this!
Run dependency command any time
The gleam run
command accepts a --module
flag, which can be used to run a
main
function from any module in your project, including modules in your
dependencies. It works by compiling your project, booting the virtual machine,
and running the specified module.
The problem with this is that if your code doesn’t compile you won’t be able to run modules in your dependencies, even if they have already compiled successfully. You would have to fix your code before you can run anything.
The build tool now skips compiling your code if you’re running a dependency module, removing this limitation and adding a slight performance improvement as less work is being done. Thank you Giacomo Cavalieri!
Prettier HTML documentation
The gleam docs build
command can be used to generate lovely searchable
documentation for your code and the documentation comments within, and when you
publish a package with gleam publish
then the HTML documentation is also
published for you.
Jiangda Wang has improved the styling of the
sidebar of this documentation. Now if you have a project with long module names
(such as my_project/web/api/v2/permissions/bucket_creation
) the text will wrap
at a /
and the continued text on the next line will be indented, making it
easier to read. Thank you Jiangda!
Prettier runtime errors
We’ve put a lot of work into making Gleam’s compile time errors as clear and understandable as possible, but when a runtime error crashes a program we’ve used the virtual machine’s default exception printing functionality, which wasn’t as clear as it could have been.
runtime error: let assert
Pattern match failed, no pattern matched the value.
unmatched value:
Error("User not logged in")
stacktrace:
my_app.-main/0-fun-0- /root/my_app/src/my_app.gleam:8
gleam/list.do_map /root/my_app/build/packages/gleam_stdlib/src/gleam/list.gleam:380
my_app.-main/0-fun-1- /root/my_app/src/my_app.gleam:11
The virtual machine doesn’t have a source-maps feature, so the line numbers may
not be perfectly accurate, but the generated code now includes per-function
annotations to improve the accuracy compared to previous versions. Additionally,
OTP application trees are now shut down gracefully when main
exits.
Gleam requirement detection
In a package’s gleam.toml
you can specify a minimum required Gleam version.
This is useful as if someone attempts to compile your package with too low a
version they will be presented with a clear error message instead of a cryptic
syntax error. Previously it was up for the programmer to keep this requirement
accurate for the code, which is error prone and rarely done.
The compiler can now infer the minimum Gleam version needed for your code to
compile and emits a warning if the project’s gleam
version constraint doesn’t
include it. For example, let’s say your gleam.toml
has the constraint
gleam = ">= 1.1.0"
and your code is using some feature introduced in a later
version:
// Concatenating constant strings was introduced in v1.4.0!
pub const greeting = "hello " <> "world!"
You would now get the following warning:
warning: Incompatible gleam version range
┌─ /root/datalog/src/datalog.gleam:1:22
│
1 │ pub const greeting = "hello " <> "world!"
│ ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.4.0
Constant strings concatenation was introduced in version v1.4.0. But the
Gleam version range specified in your `gleam.toml` would allow this code to
run on an earlier version like v1.1.0, resulting in compilation errors!
Hint: Remove the version constraint from your `gleam.toml` or update it to be:
gleam = ">= 1.4.0"
Running the gleam fix
will now update the package’s gleam
version constraint
for you automatically.
If a package has an incorrect constraint then gleam publish
will not let the
package be published until it is correct. If there’s no explicit constraint in
the gleam.toml
file then the inferred requirement will be added to the package
in the package repository for its users to benefit from.
Thank you Giacomo Cavalieri for these features!
Bit array analysis improvements
Gleam’s bit array syntax allows you to construct and parse binary data in a way
that may be easier to understand than using binary operators. Giacomo Cavalieri
has introduced some overloading to the syntax so literal unicode segments no
longer need to be annotated with utf8
.
<<"Hello", 0:size(1), "world">>
Is the same as:
<<"Hello":utf8, 0:size(1), "world":utf8>>
On JavaScript bit arrays currently have to be byte aligned. Previously invalid alignment would be a runtime error, but Richard Viney has added analysis that turns this into a compiler error instead.
Thank you Giacomo and Richard!
Context aware function inference
Anonymous functions that are immediately called with a record or a tuple as an argument are now inferred correctly without the need to add type annotations. For example you can now write:
fn(x) { x.0 }(#(1, 2))
// ^ you no longer need to annotate this!
This also includes to anonymous functions in pipelines, making it easy to access a record field without extra annotations or assigning to a variable.
pub type User {
User(name: String)
}
pub fn main() {
User("Lucy")
|> fn(user) { user.name }
// ^^^^ you no longer need to annotate this!
|> io.debug
}
Thank you sobolevn for these type inference improvements!
Helpful errors for using modules as values
Modules and values occupy two different namespaces in Gleam, so you can use the same name for a variable and an import and the compiler will pick whichever is correct for that context.
Other BEAM languages such as Erlang and Elixir have modules that can be assigned to variables and passed around as values, so sometimes folks new to Gleam can be confused and try to use a module as a value. The compiler now has a helpful error message specifically for this case.
import gleam/list
pub fn main() {
list
}
error: Module `list` used as a value
┌─ /root/prova/src/prova.gleam:4:3
│
4 │ list
│ ^^^^
Modules are not values, so you cannot assign them to variables, pass them to
functions, or anything else that you would do with a value.
Thank you sobolevn!
Helpful errors for OOP-ish syntax errors
Another common mistake is attempting to write an OOP class in Gleam. Being a functional language Gleam doesn’t have classes or methods, only data and functions. A helpful error message has been added for when someone attempts to define methods within a custom type definition.
pub type User {
User(name: String)
fn greet(user: User) -> String {
"hello " <> user.name
}
}
error: Syntax error
┌─ /root/prova/src/prova.gleam:8:3
│
8 │ fn greet(user: User) -> String {
│ ^^ I was not expecting this
Found the keyword `fn`, expected one of:
- `}`
- a record constructor
Hint: Gleam is not an object oriented programming language so
functions are declared separately from types.
Thank you sobolevn!
Missing import suggestions
If some code attempts to use a module that has not been imported yet then the compile error will suggest which module you may want to import to fix the problem. This suggestion checks not only the name of the module but also whether it contains the value you are attempting to access. For example, this code results in this error with a suggestion:
pub fn main() {
io.println("Hello, world!")
}
error: Unknown module
┌─ /src/file.gleam:2:3
│
2 │ io.println("Hello, world!")
│ ^^
No module has been found with the name `io`.
Hint: Did you mean to import `gleam/io`?
However this code does not get a suggestion in its error message:
pub fn main() {
io.non_existent()
}
When there is a suitable module to suggest the language server offers a code action to add an import for that module to the top of the file.
pub fn main() {
io.println("Hello, world!")
}
// After
import gleam/io
pub fn main() {
io.println("Hello, world!")
}
Thank you Surya Rose for this!
Helpful invalid external target errors
Jiangda Wang has added a helpful error message for when the specified target for an external function is unknown. Thank you Jiangda!
error: Syntax error
┌─ /root/my_app/src/my_app.gleam:5:1
│
5 │ @external(elixir, "main", "run")
│ ^^^^^^^^^ I don't recognise this target
Try `erlang`, `javascript`.
Helpful “if” expression errors
Gleam has one single flow-control construct, the pattern matching case
expression. The compiler now shows an helpful error message if you try writing
an if
expression instead of a case. For example, this code:
pub fn main() {
let a = if wibble {
1
}
}
error: Syntax error
┌─ /src/parse/error.gleam:3:11
│
3 │ let a = if wibble {
│ ^^ Gleam doesn't have if expressions
If you want to write a conditional expression you can use a `case`:
case condition {
True -> todo
False -> todo
}
See: https://tour.gleam.run/flow-control/case-expressions/
Thank you Giacomo Cavalieri!
Implicit todo
formatting
If you write a use
expression
without any more code in that block then the compiler implicitly inserts a
todo
expression. With this release the Gleam code formatter will insert that
todo
for you, to make it clearer what is happening.
// Before
pub fn main() {
use user <- result.try(fetch_user())
}
// After
pub fn main() {
use user <- result.try(fetch_user())
todo
}
Thank you to our formatter magician Giacomo Cavalieri!
Result discarding code action
The compiler will warn if a function returns a Result
and that result
value is not used in any way, meaning that the function could have failed and
the code doesn’t handle that failure.
Jiangda Wang has added a language server code
action to assign this unused result to _
for times when you definitely do not
care if the function succeeded or not. Thank you!
// Before
pub fn main() {
function_which_can_fail()
io.println("Done!")
}
// After
pub fn main() {
let _ = function_which_can_fail()
io.println("Done!")
}
Variable and argument completion
And last but not least, Ezekiel Grosfeld has added autocompletion for local variable and function arguments to the language server, an addition that people have been excitedly asking for for a long time. Thank you Ezekiel!
Bug bashing
An extra special shout-out to the bug hunters Ankit Goel, Giacomo Cavalieri, Gustavo Inacio, Surya Rose, and Victor Kobinski Thank you!
If you’d like to see all the changes for this release, including all the bug fixes, check out the changelog in the git repository.
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 substantially 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
- Adi Iyengar
- Adi Salimgereyev
- Adrian Mouat
- Ajit Krishna
- Alembic
- Alex
- Alex Houseago
- Alex Manning
- Alex Viscreanu
- Alexander Koutmos
- Alexander Stensrud
- Alexandre Del Vecchio
- Aliaksiej Maroz
- Ameen Radwan
- AndreHogberg
- andrew
- Andrew Brown
- Andris Horvath
- András B Nagy
- Andy Aylward
- Ankit Goel
- Antharuu
- Anthony Khong
- Anthony Maxwell
- Anthony Scotti
- areel
- Arnaud Berthomier
- Arthur Weagel
- Azure Flash
- Barry Moore
- Bartek Górny
- Ben Martin
- Ben Marx
- Ben Myles
- Benjamin Peinhardt
- Benjamin Thomas
- bgw
- Bill Nunney
- Brett Cannon
- brettkolodny
- Brian Dawn
- Brian Glusman
- Bruno Michel
- Bruno Roy
- bucsi
- Carlo Gilmar
- Carlo Munguia
- Carlos Saltos
- Chad Selph
- Charlie Govea
- Chaz Watkins
- Chew Choon Keat
- Chris Donnelly
- Chris Haynes
- Chris King
- Chris Lloyd
- Chris Ohk
- Chris Rybicki
- Christopher De Vries
- Christopher Dieringer
- Christopher Keele
- clangley
- Claudio
- CodeCrafters
- Coder
- Cole Lawrence
- Colin
- Comamoca
- Constantin (Cleo) Winkler
- Daigo Shitara
- Damir Vandic
- Dan Dresselhaus
- Daniel
- Danielle Maywood
- Danny Arnold
- Danny Martini
- Darshak Parikh
- Dave Lucia
- David Bernheisel
- David Cornu
- David Dios
- David Sancho
- Dennis Dang
- dennistruemper
- dependabot[bot]
- Dillon Mulroy
- Dima Utkin
- Dmitry Poroh
- Donnie Flood
- ds2600
- ducdetronquito
- Duncan Holm
- Dusty Phillips
- Dylan Carlson
- Edon Gashi
- eeeli24
- Eileen Noonan
- eli
- Emma
- EMR Technical Solutions
- Eric Koslow
- Erik Terpstra
- erikareads
- ErikML
- Ernesto Malave
- Evaldo Bratti
- Evan Johnson
- evanasse
- ezegrosfeld
- Fede Esteban
- Felix Mayer
- Fernando Farias
- Filip Figiel
- Florian Kraft
- Frank Wang
- G-J van Rooyen
- Gareth Pendleton
- GearsDatapacks
- Georg H. Ekeberg
- Giacomo Cavalieri
- Giovanni Kock Bonetti
- Graeme Coupar
- graphiteisaac
- grotto
- Guilherme de Maio
- Guillaume Hivert
- Gustavo Inacio
- Hammad Javed
- Hannes Nevalainen
- Hannes Schnaitter
- Hans Fjällemark
- 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
- inoas
- Isaac Harris-Holt
- Isaac McQueen
- Ismael Abreu
- Ivar Vong
- J. Rinaldi
- Jack Peregrine Doust
- Jacob Lamb
- Jake Cleary
- James Birtles
- James MacAulay
- Jan Skriver Sørensen
- Jean-Luc Geering
- Jen Stehlik
- Jenkin Schibel
- Jesse Tham
- jiangplus
- Jimpjorps™
- Joey Kilpatrick
- Johan Strand
- John Björk
- John Gallagher
- John Pavlick
- Jon Lambert
- Jonas E. P
- Jonas Hedman Engström
- Jonathan Arnett
- jooaf
- Jorge Martí Marín
- Joseph Lozano
- Joshua Steele
- Juhan
- Julian Lukwata
- Julian Schurhammer
- Justin Lubin
- Kero van Gelder
- Kevin Schweikert
- Kirill Morozov
- kodumbeats
- Kramer Hampton
- Kryštof Řezáč
- Krzysztof G.
- Leandro Ostera
- LelouchFR
- Leon Qadirie
- Leonardo Donelli
- lidashuang
- LighghtEeloo
- Lily Rose
- Loïc Tosser
- Lucas Pellegrinelli
- Lucian Petic
- Lukas Meihsner
- Luke Amdor
- Luna
- Manav
- Manuel Rubio
- Marcus André
- Marcøs
- Mariano Uvalle
- Marius Kalvø
- Mark Holmes
- Mark Markaryan
- Markéta Lisová
- Martin Janiczek
- Martin Rechsteiner
- martonkaufmann
- Mathieu Darse
- Matt Champagne
- Matt Robinson
- Matt Savoia
- Matt Van Horn
- Matthias Benkort
- Max Hill
- Max McDonnell
- max-tern
- Michael Duffy
- Michael Jones
- Michael Kieran O’Reilly
- Michael Kumm
- Michael Mazurczak
- Michał Hodur
- Mihlali Jordan
- Mike
- Mike Nyola
- Mike Roach
- Mikey J
- MoeDev
- Moritz Böhme
- MzRyuKa
- Måns Östman
- n8n - Workflow Automation
- Natanael Sirqueira
- Nathaniel Johnson
- Nathaniel Knight
- NFIBrokerage
- Nick Chapman
- Nick Reynolds
- Nicklas Sindlev Andersen
- NicoVIII
- Niket Shah
- Ninaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- NineFX
- Nomio
- Ocean
- OldhamMade
- Oliver Medhurst
- optizio
- oscar
- Osman Cea
- PastMoments
- Patrick Wheeler
- Paul Gideon Dann
- Paul Guse
- Paul Kuruvilla
- Paulo Vidal
- Pawel Biernacki
- Pete Jodo
- Peter Rice
- Petri-Johan Last
- PgBiel
- Philip Giuliani
- Pierrot
- Piotr Szlachciak
- porkbrain
- Qdentity
- qexat
- qingliangcn
- Race Williams
- Rahul Butani
- Ray
- Raúl Chouza
- re.natillas
- Redmar Kerkhoff
- Reilly Tucker Siemens
- Renovator
- Richard Viney
- Rico Leuthold
- Ripta Pasay
- Rob
- Robert Attard
- Robert Ellen
- Robert Malko
- rockerBOO
- Rodrigo Heinzen de Moraes
- Rupus Reinefjord
- Ruslan Ustitc
- Sam Aaron
- sambit
- Sammy Isseyegh
- Samu Kumpulainen
- Santi Lertsumran
- Savva
- Saša Jurić
- Scott Trinh
- Scott Wey
- Sean Jensen-Grey
- Sebastian Porto
- sekun
- Seve Salazar
- Shane Poppleton
- Shuqian Hon
- Simone Vittori
- sobolevn
- Spec
- star-szr
- Stefan
- Stephen Belanger
- Steve Powers
- Strandinator
- Sunil Pai
- syhner
- Sławomir Ehlert
- Theo Harris
- Thomas
- Thomas Coopman
- Thomas Ernst
- Thomas Teixeira
- Tim Brown
- Timo Sulg
- Tom Schuster
- Tomasz Kowal
- tommaisey
- Tristan de Cacqueray
- Tristan Sloughter
- upsidedowncake
- Valerio Viperino
- Vassiliy Kuzenkov
- versecafe
- Vic Valenzuela
- Victor Rodrigues
- Vincent Costa
- Viv Verner
- vkobinski
- Volker Rabe
- Weizheng Liu
- Wesley Moore
- Willyboar
- Wilson Silva
- Yamen Sader
- Yasuo Higano
- yoshi~
- Zack Sargent
- Zhomart Mukhamejanov
- Zsombor Gasparin
- ~1847917
Thanks for reading, I hope you have fun with Gleam! 💜