Published 05 Jan, 2025 by Louis Pilfold
Gleam is a type-safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.7.0 has been published, featuring an array of wonderful improvements. Let’s take a look!
Faster record updates
Gleam is a language with immutable data, and it has a syntax for creating a new record from an old one with some updated fields.
/// Create a new version of the user with `admin` set to true.
pub fn make_admin(person: User) {
User(..person, admin: True)
}
If you’re familiar with JavaScript this is similar to the object spread update syntax, and similarly it is fast, only copying the references to the fields, not the data itself.
The code that the Gleam compiler would generate would also be similar to how JavaScript’s update works, using a small amount of dynamic code at runtime to construct the new record with the new fields. This runtime conditional logic had a small performance cost at runtime.
The compiler now instead monomorphises record updates, meaning it generates exactly the most efficient code to construct the new record on a case-by-case basis, removing the runtime conditional logic and its associated cost entirely.
The optimisation is for both the Erlang and the JavaScript targets, has no additional compile speed cost or increase in code size, so it’s an improvement in every way!
Another benefit of record update monomorphisation is that you can now change the parameterised types of a generic record with the update syntax.
pub type Named(element) {
Named(name: String, value: element)
}
pub fn replace_value(data: Named(a), replacement: b) -> Named(b) {
Named(..data, value: replacement)
}
Previously this would not compile as the type parameter changed, and the compiler wasn’t able to infer it was always done safely. Now it can tell, so this compiles!
Thank you yoshi for this excellent change!
Generate decoder code action
Gleam has a very robust type system, it won’t let you unsafely cast values between different types. This results in a programming experience where the compiler and language server can offer lots help to the programmer, especially in unfamiliar or large codebases.
One drawback of this sound type system is that converting untyped input from the outside world into data of known types requires some additional code which would not be required in unsound systems. This decoder code can be unfamiliar and confusing to those new to Gleam, and in simple cases it can seem a chore.
To aid with this the Gleam language server now includes code action to generate a dynamic decoder for a custom type. For example, if you have this code:
pub type Person {
Person(name: String, age: Int)
}
If you place your cursor on the type header and select the code action in your editor, then it’ll be updated to this:
import gleam/dynamic/decode
pub type Person {
Person(name: String, age: Int)
}
fn person_decoder() -> decode.Decoder(Person) {
use name <- decode.field("name", decode.string)
use age <- decode.field("age", decode.int)
decode.success(Person(name:, age:))
}
Thank you Surya Rose! I know this will be a very popular addition.
More secure package manager credential handling
Gleam is part of the Erlang ecosystem, so it uses the Hex package manager. To publish a package to Hex the build tool needs the credentials for your Hex account, and you would type them into the command line to supply them. We make this as secure as possible, but there’s always some risk when typing in credentials. No amount of in-computer security can save you from someone sitting behind you, watching your fingers on the keyboard.
Gleam now only asks for your Hex credentials once, and uses that to create a long-lived API token, which will be stored on your filesystem and encrypted using a local password of your choosing. For all future interactions with Hex Gleam will ask for the local password, use that to decrypt the API key, and then use it to authenticate with the Hex APIs.
With this if someone manages to learn the password you use to Hex they would not be able to do anything with it unless they can also get access to your computer and the encrypted file stored on it.
Package namespace checking
The Erlang virtual machine has a single namespace for modules. It does not have isolation of modules between different packages, so if two packages define modules with the same name they can collide and cause a build failure or other undesired behaviour.
To avoid this packages place their modules within their own namespace. For
example, if I am writing a package named pumpkin
I would place my modules
within the src/pumpkin/
directory.
Sometimes people from other ecosystems with per-package isolation may not
understand this convention and place all their code in the top-level namespace,
using generic names, which results in problems for any users of their package. To
avoid this the gleam publish
command will now check for top-level namespace
pollution, explaining the problem and asking for confirmation if it is present.
Thank you Aleksei Gurianov!
Core team package name checking
The Hex package manager system doesn’t have namespaces, so we can’t publish
packages maintained by the Gleam core team as @gleam/*
or such. Instead Hex
users have to rely on adding a prefix to the names of their packages, and in
the Gleam core team we use the prefix gleam_
.
These prefixes are unchecked, so one can use anyone else’s prefix without
issue. This is a problem for us as people occasionally publish packages using
the core team’s prefix, and then other people get confused as to why this
seemingly official package is of a low quality. To try and remedy this Gleam
will ask for confirmation when a package is published with the gleam_
prefix.
Unfortunately this was not enough and another unofficial package was
accidentally published, so Gleam now asks for a much longer confirmation to be
typed in, to force the publisher to read the message.
Semantic versioning encouragement
Sometimes people like to publish packages that are unfinished or unsuitable for use by others, publishing them as version 0.*. Other people publish code that is good to use, but shy away from semantic versioning and publish them as v0.*. In both of these cases the users of these packages have an inferior experience, unable to take advantage of the benefits that semantic versioning is designed to bring, which can lead to irritating build errors.
Gleam will now ask for confirmation if a package is published with a v0.* version, as it does not respect semantic versioning. The fewer zero-version packages published the better experience users of the package ecosystem will have.
Variant deprecation
In Gleam one can deprecate functions and types using the @deprecated
attribute, which causes the compiler to emit a warning if they are used. With
this release it is also possible to deprecate individual custom type variants
too!
pub type HashAlgorithm {
@deprecated("Please upgrade to another algorithm")
Md5
Sha224
Sha512
}
pub fn hash_password(input: String) -> String {
hash(input:, algorithm: Md5) // Warning: Deprecated value used
}
Thank you Iesha for this!
Canonical documentation links
When packages are published to Hex Gleam will also generate HTML documentation and upload it to HexDocs, the documentation hosting site for the BEAM ecosystem.
Currently we have a problem where Google is returning documentation for very old versions of Gleam libraries instead of the latest version, which can result in confusion as people try to use functions that no longer exist, etc. To prevent this from happening with future versions Gleam now adds a canonical link when publishing, which should help search engines return the desired version. In the near future we will write a tool that will update historical documentation to add these links too.
Thank you Dave Lage for this improvement!
Custom messages for pattern assertions
Gleam’s let assert
allows you to pattern match with a partial pattern, that
is: one that doesn’t match all possible values a type could be. When the value
does not match the program it crashes the program, which is most often used in
tests or in quick scripts or prototypes where one doesn’t care to implement
proper error handling.
With this version the as
syntax can be used to add a custom error message for
the crash, which can be helpful for debugging when the unexpected does occur.
let assert Ok(regex) = regex.compile("ab?c+") as "This regex is always valid"
Thank you Surya Rose!
JavaScript bit array compile time evaluation
Gleam’s bit array literal syntax is a convenient way to build up and to pattern match on binary data. When targeting JavaScript the compiler now generates faster and smaller code for int values in these bit array expressions and patterns by evaluating them at compile time where possible.
Thank you Richard Viney for this!
JavaScript bit array slicing optimisation
Continuing on from his previous bit array optimisation, Richard Viney has made taking a sub-slice of a bit array a constant time operation on JavaScript, to match the behaviour on the Erlang target. This is a significant improvement to performance.
Thank you Richard! Our bit array magician!
Empty blocks are valid!
In Gleam one can write an empty function body, and it is considered a not-yet-implemented function, emitting a warning when compiled. This is useful for when writing new code, where you want to check some things about your program but have not yet finished writing it entirely.
pub fn wibble() {} // warning: unimplemented function
You can now also do the same for blocks, leaving them empty will result in a compile warning but permit you to compile the rest of your code.
pub fn wibble() {
let x = {
// warning: incomplete block
}
io.println(x)
}
Thank you Giacomo Cavalieri!
External modules in subdirectories
Gleam has excellent interop with Erlang, Elixir, JavaScript, and other languages running on its target platforms. Modules in these languages can be added to your project and imported using Gleam’s external functions feature.
Previously these external modules had to be at the top level of the src/
or
test/
directories, but now they can reside within subdirectories of them too.
Thank you PgBiel for this long-awaited feature!
Installation hints
To run Gleam on the BEAM an Erlang installation is required, and to run it on JavaScript a suitable runtime such as NodeJS is required. To initialise a repository git is required. To compile Elixir code Elixir must be installed. You get the idea- to use various external tools they need to be installed.
If there’s a particular recommended way to install a missing component for your operating system the error message for its absence will now direct you to install it that way.
error: Shell command failed
The program `elixir` was not found. Is it installed?
You can install Elixir via homebrew: brew install elixir
Documentation for installing Elixir can be viewed here:
https://elixir-lang.org/install.html
Thank you wheatfox for this helpful improvement!
Faster Erlang dependency compilation
You can add packages written in Erlang or Elixir to your Gleam projects, and the Gleam build tool will compile them for you. To compile Erlang packages rebar3, the Erlang build tool, is used.
Gleam now sets the REBAR_SKIP_PROJECT_PLUGINS
environment variable
when using rebar3. With future versions of rebar3 this will cause it to skip
project plugins, significantly reducing the amount of code it’ll need to
download and compile, improving compile times.
Thank you to Tristan Sloughter for this contribution to both Gleam and rebar3! Elixir’s Mix build tool will also benefit from this new rebar3 feature.
Sugar and desugar use
expressions
Gleam’s use
expression is a much loved and very useful bit of syntactic
sugar, good for making nested higher-order-functions easy to work with. It is
by-far Gleam’s most unusual feature, so it can take a little time to get a good
understanding of it.
To help with this, and to make refactoring easier, Jak has added two new code
actions to the language server, to convert to and from the use
expression
syntax and the equivalent using the regular function call syntax.
Here’s the same code in each syntax, so you can get an idea of what the code actions will convert to and from for you.
pub fn main() {
use profile <- result.try(fetch_profile(user))
render_welcome(user, profile)
}
pub fn main() {
result.try(fetch_profile(user), fn(profile) {
render_welcome(user, profile)
})
}
Thank you Giacomo Cavalieri for these!
Yet more language server hover information
Surya Rose has been adding more hover information to the language server. If you hover over patterns or function labels in your editor then type and documentation information will be shown. Thank you Surya!
Inexhaustive let
to case
code action
Using a partial pattern that does not match all possible values with a let
binding is a compile error in Gleam.
pub fn unwrap_result(result: Result(a, b)) -> a {
let Ok(inner) = result // error: inexhaustive
inner
}
The language server now suggests a code action to convert this let
into a
case
expression with the missing patterns added, so you can complete the
code.
pub fn unwrap_result(result: Result(a, b)) -> a {
let inner = case result {
Ok(inner) -> inner
Error(_) -> todo
}
inner
}
Thanks again Surya Rose!
Extract variable code action
The language server now provides an action to extract a value into a variable. Given this code:
pub fn main() {
list.each(["Hello, Mike!", "Hello, Joe!"], io.println)
}
If you place your cursor on the list and trigger the code action in your editor the code will be updated to this:
pub fn main() {
let value = ["Hello, Mike!", "Hello, Joe!"]
list.each(value, io.println)
}
Thank you Giacomo Cavalieri for this!
Expand function capture code action
Gleam has a short-hand syntax for a function that takes a single argument and
passes it to another function, along with some other arguments. Here you can
see it being used with the int.add
function to create a function that always
adds 11.
pub fn main() {
let add_eleven = int.add(_, 11)
list.map([1, 2, 3], add_eleven)
}
Triggering the code action results in the function-capture being expanded to the full anonymous function syntax:
pub fn main() {
list.map([1, 2, 3], fn(value) { int.add(value, 11) })
}
Thank you Giacomo Cavalieri for the final code action of the release!
And the rest
And thank you to the bug fixers and error message improvers Giacomo Cavalieri, Ivan Ermakov, Jiangda Wang, John Strunk, PgBiel, Richard Viney, Sakari Bergen, Surya Rose, and yoshi
For full details of the many fixes and improvements they’ve implemented see the changelog.
It’s my birthday 🎁
Today is my birthday! If you’d like to give me a gift please consider supporting Gleam on GitHub Sponsors.
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, and Gleam is my sole source of income.
Giacomo Cavalieri is also deserving of your support. He has been doing amazing work on Gleam without any pay for nearly two years, but now he has GitHub sponsors, so show him some love!
Thank you to all our sponsors, especially our top sponsor: Lambda.
- 00bpa
- Aaron Gunderson
- Abdulrhman Alkhodiry
- Abel Jimenez
- ad-ops
- Adam Brodzinski
- Adam Johnston
- Adam Wyłuda
- Adi Iyengar
- 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
- Antharuu
- Anthony Khong
- Anthony Maxwell
- Anthony Scotti
- Arthur Weagel
- Arya Irani
- Azure Flash
- Barry Moore
- Bartek Górny
- Ben Martin
- Ben Marx
- Ben Myles
- Benjamin Kane
- Benjamin Peinhardt
- Benjamin Thomas
- bgw
- Bill Nunney
- Bjarte Aarmo Lund
- Brad Mehder
- brettkolodny
- Brian Dawn
- Brian Glusman
- Bruce Williams
- Bruno Michel
- bucsi
- Cam Ray
- Cameron Presley
- Carlo Munguia
- Carlos Saltos
- Chad Selph
- Charlie Duong
- Charlie Govea
- Chaz Watkins
- Chew Choon Keat
- Chris Donnelly
- Chris King
- Chris Lloyd
- Chris Ohk
- Chris Rybicki
- Christopher David Shirk
- Christopher De Vries
- Christopher Dieringer
- Christopher Jung
- Christopher Keele
- CJ Salem
- clangley
- Clifford Anderson
- CodeCrafters
- Coder
- Cole Lawrence
- Colin
- Comamoca
- Constantin (Cleo) Winkler
- Corentin J.
- Cristiano Carvalho
- Daigo Shitara
- Damir Vandic
- Dan Dresselhaus
- Danielle Maywood
- Danny Arnold
- Danny Martini
- Dave Lucia
- David Bernheisel
- David Cornu
- David Sancho
- Dennis Dang
- dennistruemper
- Diemo Gebhardt
- Dillon Mulroy
- Dima Utkin
- Dmitry Poroh
- DoctorCobweb
- Donnie Flood
- ds2600
- ducdetronquito
- Dylan Carlson
- Edon Gashi
- eeeli24
- Eileen Noonan
- eli
- Emma
- EMR Technical Solutions
- Endo Shogo
- Eric Koslow
- Erik Terpstra
- erikareads
- ErikML
- erlend-axelsson
- 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
- Ganesan Janarthanam (Jana)
- Geir Arne Hjelle
- Georg H. Ekeberg
- Georg Hartmann
- George
- ggobbe
- Giacomo Cavalieri
- Giovanni Kock Bonetti
- Graeme Coupar
- grotto
- Guilherme de Maio
- Guillaume Heu
- Guillaume Hivert
- Hammad Javed
- Hannes Nevalainen
- Hannes Schnaitter
- 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 Pieper
- Jan Skriver Sørensen
- Jean-Luc Geering
- Jen Stehlik
- jiangplus
- Jimpjorps™
- Joey Kilpatrick
- Joey Trapp
- Johan Strand
- John Björk
- John Gallagher
- John Pavlick
- Jojor
- Jon Lambert
- Jonas E. P
- Jonas Hedman Engström
- jooaf
- Joseph Lozano
- Joshua Steele
- Julian Lukwata
- Julian Schurhammer
- Justin Lubin
- Jérôme Schaeffer
- Kemp Brinson
- Kero van Gelder
- Kevin Schweikert
- Kramer Hampton
- Kritsada Sunthornwutthikrai
- Kryštof Řezáč
- Krzysztof G.
- 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
- Marcos
- marcusandre
- Mariano Uvalle
- Marius Kalvø
- Mark Holmes
- Mark Markaryan
- Markéta Lisová
- 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
- METATEXX GmbH
- Metin Emiroğlu
- Michael Duffy
- Michael Jones
- Michael Mazurczak
- Michał Łępicki
- Mikael Karlsson
- Mike
- Mike Roach
- Mikey J
- MoeDev
- Moritz Böhme
- MzRyuKa
- n8n - Workflow Automation
- Natanael Sirqueira
- Nathaniel Knight
- Nayuki
- NFIBrokerage
- Nicholas Moen
- Nick Chapman
- Nick Reynolds
- Nicklas Sindlev Andersen
- NicoVIII
- Niket Shah
- Ninaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- NineFX
- Nomio
- Ocean
- Olaf Sebelin
- OldhamMade
- Oliver Medhurst
- Oliver Tosky
- optizio
- Osman Cea
- PastMoments
- Patrick Wheeler
- Paul Gideon Dann
- Paul Guse
- Pawel Biernacki
- Pedro Correa
- Pete Jodo
- Peter Rice
- Philpax
- Pierrot
- Piotr Szlachciak
- Qdentity
- Race Williams
- Rasmus
- Ray
- Raúl Chouza
- re.natillas
- 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
- Sam Aaron
- Sam Zanca
- sambit
- Sami Fouad
- Sammy Isseyegh
- Savva
- Saša Jurić
- Scott Trinh
- Scott Weber
- Scott Wey
- Sean Jensen-Grey
- Sean Roberts
- Sebastian Porto
- sekun
- Seve Salazar
- Shane Poppleton
- Shuqian Hon
- Simone Vittori
- star-szr
- Stefan
- Stefan Hagen
- Stephen Belanger
- Steve Powers
- Strandinator
- Sunil Pai
- Sławomir Ehlert
- Theo Harris
- Thomas
- Thomas Coopman
- Thomas Ernst
- Tim Brown
- Timo Sulg
- Tom Calloway
- Tom Schuster
- Tomasz Kowal
- tommaisey
- Travis Johnson
- Tristan de Cacqueray
- Tristan Sloughter
- tymak
- upsidedowncake
- Valerio Viperino
- Vic Valenzuela
- Victor Rodrigues
- Viv Verner
- Volker Rabe
- Weizheng Liu
- wheatfox
- Willyboar
- Wilson Silva
- Xucong Zhan
- Yamen Sader
- Yasuo Higano
- yoshi~
- Yuriy Baranov
- Zsombor Gasparin
- ~1814730
- ~1847917
- ~1867501
- Éber Freitas Dias
Thanks for reading, I hope you have fun with Gleam! 💜