Gleam is a type safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.15.0 has been published, let's go over what's new.
The Hex package manager
Gleam is a BEAM language, so it uses Hex, the package manager of the Erlang ecosystem. Hex recently introduced a new OAuth2 based authentication system, replacing the old system of exchanging the account's username and password for a long-lived token. This new system has many security advantages:
- Multi-factor authentication is used for all endpoints that require write permissions, so stolen credentials or tokens are insufficient to perform sensitive actions.
- Hex clients such as Gleam never see your password, so there is less opportunity for an attacker to harvest credentials given access to your workstation.
- The access tokens are short lived, so if stolen there is only a small window in which they could be used by the attacker.
- The Hex site can integrate with other OAuth providers, enabling companies to use their existing identity management and audit systems to secure Hex.
Gleam now uses exclusively this new system for authentication, and any existing legacy tokens that Gleam has stored locally will be revoked the first time that the new version of Gleam is used with Hex. The legacy system will be disabled for Gleam packages in future.
Lastly, the password used for encrypting local Hex tokens must now be at least 8 characters in length.
This work was done in collaboration with and was sponsored by The Erlang Ecosystem Foundation. A huge thank-you to Jonatan Männchen and the rest of the team there! If you would like to support important infrastructure projects for the whole ecosystem consider becoming a member of the foundation.
Better Hex errors
It's possible for Hex operations to fail, for example, attempting to depend on a package that does not exist, or attempting to publish a new version of a package that you are not the maintainer of. When this happens we typically show the programmer the error message as-is from the Hex API. These error messages are not bad, but compared to the other error messages from Gleam it can be not immediately obvious what the problem is and how the programmer should proceed.
Ameen Radwan and vyacheslavhere have added custom errors for these two scenarios, fixing the two main stumbling points folks have with Hex errors. Thank you both!
Guard clause ergonomics
Adi Salimgereyev has implemented two nice improvements to guard expressions in case expressions. Firstly, the string concatenation operator can now be used:
case message {
action if version <> ":" <> action == "v1:delete" -> handle_delete()
_ -> ignore_command()
}
And a helpful custom error message is now shown when int and float binary operators are used incorrectly in case expression guards.
Thank you Adi Salimgereyev!
Additionally, the language server now supports renaming, go to definition, hover, and finding references from expressions in case clause guards. Thank you Surya Rose!
Internal types presentation
Gleam's internal publicity level allows definitions to be technically importable and usable from other modules, but not be part of the actual public API of the module. This may be useful for creating "escape hatch" APIs, or for facilitating shared functionality between modules owned by the same maintainer.
Because they are not public, these internal definitions are not shown in the generated API documentation, and are not suggested for autocompletion by the language server. There was however some compiler and language server functionality where they did not get special treatment, which could make it not immediately obvious that a definition is internal. Giacomo Cavalieri has fixed these now!
The "Add missing patterns" code action will insert a catch all pattern for internal types, the language server will no longer show completions for the fields of internal types, and the compiler no longer shows the structure of internal types when displaying an "Inexhaustive patterns" error. Thank you Jak!
JavaScript FFI additions
Gleam can compile to JavaScript, making use of the wealth of code that exists in the JavaScript ecosystem. To enable JavaScript code to construct and work with Gleam data structures the compiler provides an API for each type in a Gleam codebase. This release brings some improvements to this API:
The BitArray$isBitArray and BitArray$BitArray$data functions have been added,
enabling JavaScript to consume the Gleam prelude bit-array type.
import { BitArray$isBitArray, BitArray$BitArray$data } from "../gleam.mjs";
export function writeFile(path, data) {
if (BitArray$isBitArray(data)) {
const buffer = BitArray$BitArray$data(data);
return fs.write(path, buffer);
}
}
TypeScript types are also generated for this API. Functions such as
BitArray$isBitArray that check if a value is of a given type now have the
return type value is TypeName, so the TypeScript type checker can use these
functions to understand whether or not the value is the expected Gleam type.
Package quality publish checks
We want the Hex package repository to be full of high-quality and production-ready code. It is not a place for sharing or showing-off prototypes, and name-squatting is against the terms of service. We keep an eye on the packages that are published over time, monitoring for any undesirable trends that we wish to discourage, and introducing checks to prevent them occasionally.
One problem we noticed recently was folks opting to not document their package
with a README, which is intended to serve as the home-page of the
documentation. - The build tool will now refuse to publish any package that has
the default README generated by the gleam new command, or is missing any
README altogether.
Thank you Giacomo Cavalieri!
Code folding
The language server now supports textDocument/foldingRange, enabling folding
for contiguous import blocks and multi-line top-level definitions, such as
function bodies, custom types, constants, and type aliases.
import gleam/int
import gleam/list
import gleam/string
This block of imports can now be folded in the editor like so:
import gleam/int ...
Thank you Aayush Tripathi!
CLI documentation improvements
The help command and --help flag can be used with Gleam's command line
interface to view a concise overview of various commands. This was helpful for
the very basics, but typically the programmer would need to refer to the
documentation on the Gleam website for more complex tasks, such as adding a
dependency from git, or adding a package with a specific version constraint.
Stopping what you are doing to go and find a web page is annoying, so the
gleam help add, gleam help deps, and gleam help docs commands have
been improved with much more detailed documentation output, covering these
sorts of tasks.
Consistent configuration
Gleam tooling uses the gleam.toml file for configuration. Due to a mistake
very early in Gleam's initial development there has been an odd inconsistency
with the names of two of the keys in this file: dev-dependencies and
tag-prefix. They use sausage-case, but all the others use snake_case!
With this release they are now canonically snake case, making them consistent. The old format continues to be supported, but it is deprecated.
name = "my_app"
version = "2.4.0"
[dependencies]
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
[dev_dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
Extract anonymous function
The Extract function code action now has a special-case for extracting
anonymous functions, using the body of the anonymous function as the body of
the new extracted function.
pub fn double(numbers: List(Int)) -> List(Int) {
let multiplier = 2
list.map(numbers, fn(number) { number * multiplier })
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
When this anonymous function is extracted the code is refactored like so:
pub fn double(numbers: List(Int)) -> List(Int) {
let multiplier = 2
list.map(numbers, function(_, multiplier))
}
fn function(number: Int, multiplier: Int) -> Int {
number * multiplier
}
Thank you Hari Mohan!
Module usage renaming
The rename action can be triggered on usages of a module, rather than just its import, for example:
import lustre/element
import lustre/element/html
import lustre/event
fn view(model: Int) -> element.Element(Msg) {
// ^ Renaming module to `el` here
let count = int.to_string(model)
html.div([], [
html.button([event.on_click(Incr)], [element.text(" + ")]),
html.p([], [element.text(count)]),
html.button([event.on_click(Decr)], [element.text(" - ")]),
])
}
Using renaming when hovering on module name would result in the following code:
import lustre/element as el
import lustre/element/html
import lustre/event
fn view(model: Int) -> el.Element(Msg) {
let count = int.to_string(model)
html.div([], [
html.button([event.on_click(Incr)], [el.text(" + ")]),
html.p([], [el.text(count)]),
html.button([event.on_click(Decr)], [el.text(" - ")]),
])
}
Thank you Vladislav Shakitskiy!
Add missing type parameter code action
The language server now suggests a quick-fix code action for when a custom type definition uses a type parameter in its variants that have not been declared in its header.
pub type Store {
Store(name: String, data: inner_type)
}
This code doesn't compile as the inner_type type variable hasn't been
declared. Running the code action results in the code being fixed like so:
pub type Store(inner_type) {
Store(name: String, data: inner_type)
}
Thank you Andi Pabst!
String prefix language server triggering
It's now possible to find references and rename variables in string prefix patterns:
case wibble {
"1" as digit <> rest -> digit <> rest
// ^^^^^ ^^^^
// You can now trigger "Find references" and "Rename" from here
}
Thank you Igor Castejón!
More precise completions
The language server protocol has some ambiguities in places, which means that
different editors can have slightly different behaviour for the same actions.
One way in which this has caused a problem for Gleam programmers is when using
a completion where the suffix already exists in the code. For example, imagine
you're updating your code to fully qualify the uses of the Json type:
pub fn payload() -> js|Json
// ^ typing the module name
Accepting the json.Json completion in some editors would result in the
incorrect code json.JsonJson. To prevent this annoyance the language server
now gives more precise instructions to the editor, so all will render the
correct json.Json code.
Thank you Giacomo Cavalieri!
Signature help type parameter names
The language server now shows the original names used for type parameters in the signature help information. This can help understand what each of the parameters mean. For example:
pub fn twice(data: input, function: fn(input) -> output) -> output {
function(function(data))
}
pub fn main() {
twice( )
// ↑ Cursor here
}
This will show this signature help information:
wibble(input, fn(input) -> output)
Thank you Samuel Cristobal!
Other language server improvements
Developer experience and productivity are our main focus, so we have lots of small but impactful quality-of-life improvements in the language server.
The language server now suggests completions for keywords that are expressions,
like echo, panic, and todo. Thank you Giacomo Cavalieri.
The "Fill labels" code action now uses variables from scope when they match
the label name and expected type, instead of leaving a todo placeholder that
the programmer would need to replace with those variables. Thank you
Vladislav Shakitskiy.
The "Interpolate String" code action now lets the user "cut out" any portion of a string, regardless of whether it is a valid Gleam identifier or not. Thank you Hari Mohan.
The language server now performs best-effort zero value generation for
decode.failure and has support for Nil when using the generate dynamic decoder
code action. Thank you Hari Mohan.
The language server now allows extracting the start of a pipeline into a variable. Thank you Giacomo Cavalieri.
Custom type definitions and custom type variants now show their documentation on hover, so you can see what it looks like when rendered. Thank you Giacomo Cavalieri.
And the rest
And thank you to the bug fixers and experience polishers: acandoo, Andrey Kozhev, Christian Widlund, daniellionel01, Etienne Boutet, Gavin Morrow, Giacomo Cavalieri, Hari Mohan, John Downey, Justin Lubin, Surya Rose, vyacheslavhere, and Vladislav Shakitskiy.
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, and Gleam is my sole source of income.
We have made great progress towards our goal of being able to appropriately pay the core team members, but we still have further to go. Please consider supporting the project or core team members Giacomo Cavalieri and Surya Rose on GitHub Sponsors.
Thank you to all our sponsors! And special thanks to our top sponsors:
- Aaron Gunderson
- Aayush
- Abel Jimenez
- Aboio
- abs0luty
- acandoo
- ad-ops
- Adam Brodzinski
- Adam Daniels
- Adam Johnston
- Adam Wyłuda
- Adi Iyengar
- Adrian Mouat
- Ajit Krishna
- albertchae
- Aleksei Gurianov
- Alembic
- Alex Houseago
- Alex Kelley
- Alex Manning
- Alexander Stensrud
- Alexandre Del Vecchio
- Ameen Radwan
- Andi
- Andrey
- André Mazoni
- Andy Young
- Antharuu
- Anthony Scotti
- Antonio Farinetti
- Arthur Weagel
- Arya Irani
- Azure Flash
- Baqtiar
- Barry Moore II
- Ben Martin
- Ben Marx
- Ben Myles
- Benjamin Kane
- Benjamin Moss
- bgw
- Billuc
- Bjarte Aarmo Lund
- Bjoern Paschen
- blurrcat
- Brad Mehder
- Brett Cannon
- Brett Kolodny
- Brian Glusman
- Bruce Williams
- Bruno Konrad
- bucsi
- Cameron Presley
- Carlo Munguia
- Carlos Saltos
- Chad Selph
- Charlie Govea
- Chew Choon Keat
- Chloe
- Chris Lloyd
- Chris Ohk
- Chris Olsen
- Chris Vincent
- Christian Visintin
- Christian Widlund
- Christopher David Shirk
- Christopher De Vries
- Christopher Dieringer
- Christopher Jung
- Christopher Keele
- Cian Synnott
- Clifford Anderson
- cncptpr
- Coder
- Cole Lawrence
- Comamoca
- Constantin (Cleo) Winkler
- Constantin Angheloiu
- Corentin J.
- Cris Holm
- dagi3d
- Damir Vandic
- Dan
- Dan Dresselhaus
- Dan Gieschen Knutson
- Dan Strong
- Daniel Kurz
- Daniele
- Danny Arnold
- Danny Martini
- Dave Lucia
- David Bernheisel
- David Cornu
- David Pendray
- Diemo Gebhardt
- Donnie Adams
- Dylan Anthony
- Dylan Carlson
- Éber Freitas Dias
- Ed Hinrichsen
- Ed Rosewright
- Edon Gashi
- Egor Kurtashkin
- Eileen Noonan
- Emma
- Eric Koslow
- Erik Ohlsson
- Erik Terpstra
- erikareads
- ErikML
- erlend-axelsson
- Ernesto Malave
- Ethan Olpin
- Evaldo Bratti
- Evan Johnson
- evanasse
- Fabrizio Damicelli
- Falk Pauser
- Fede Esteban
- Felix
- Fernando Farias
- Filip Figiel
- Florian Kraft
- Florian Weingartshofer
- Francis Hamel
- frankwang
- fruno
- G-J van Rooyen
- Gabriel Vincent
- Gabriela Sartori
- Gavin Morrow
- Gears
- Geir Arne Hjelle
- Giacomo Cavalieri
- ginkogruen
- Giovanni Kock Bonetti
- Graham
- Grant Everett
- Guilherme de Maio
- Guillaume Heu
- H.J.
- Hannes Nevalainen
- Hans Raaf
- Hari Mohan
- Harry Bairstow
- Hazel Bachrach
- Henning Dahlheim
- Henrik Tudborg
- Henry Warren
- Hizuru3
- Hubert Małkowski
- Iain H
- Ian González
- Ian M. Jones
- Igor Montagner
- IgorCastejon
- inoas
- Isaac
- Isaac Harris-Holt
- Isaac McQueen
- iskrisis
- Ivar Vong
- Jachin Rupe
- Jake Cleary
- Jake Wood
- James Birtles
- James MacAulay
- Jan Fooken
- Jan Pieper
- Jan Skriver Sørensen
- Jean Niklas L'orange
- Jean-Adrien Ducastaing
- Jean-Luc Geering
- Jen Stehlik
- Jerred Shepherd
- Jimmy Utterström
- Joey Kilpatrick
- Joey Trapp
- Johan Strand
- John Björk
- John Downey
- John Strunk
- Jojor
- Jon Charter
- Jon Lambert
- Jonas E. P
- Jonas Hedman Engström
- jooaf
- Joseph Lozano
- Joseph Myers
- Joseph T. Lyons
- Joshua Steele
- jstcz
- Julian Hirn
- Julian Lukwata
- Julian Schurhammer
- Justin Lubin
- Jérôme Schaeffer
- Jørgen Andersen
- kadei
- KamilaP
- Kemp Brinson
- Kero van Gelder
- Kevin Schweikert
- Kile Deal
- Kramer Hampton
- Kristoffer Grönlund
- Kristoffer Grönlund
- Krzysztof Gasienica-Bednarz
- Kuma Taro
- Landon
- Leah Ulmschneider
- Lee Jarvis
- Lennon Day-Reynolds
- Leon Qadirie
- Leonardo Donelli
- Lexx
- lidashuang
- Lucy McPhail
- Lukas Bjarre
- Luke Amdor
- Manuel Rubio
- Mario Vellandi
- Marius Kalvø
- Mark Dodwell
- Mark Holmes
- Mark Markaryan
- Mark Rudolph
- Markus Wesslén
- Martin Janiczek
- Martin Poelstra
- Martin Rechsteiner
- Matt Heise
- Matt Mullenweg
- Matt Savoia
- Matt Van Horn
- Matthew Jackson
- Max Maxwell
- Max McDonnell
- metame
- METATEXX GmbH
- Michael Davis
- Michael Duffy
- Michael Jones
- Michael Mazurczak
- Michal Timko
- Mikael Karlsson
- Mike Roach
- Mikey J
- MoeDev
- Moin
- MzRyuKa
- N. G. Scheurich
- n8n - Workflow Automation
- Natalie Rose
- Natanael Sirqueira
- Nick Leslie
- Nicklas Sindlev Andersen
- NicoVIII
- Nigel Baillie
- Niket Shah
- Nikolai Steen Kjosnes
- Nikolas
- Ninaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- NineFX
- Noah Betzen
- Nomio
- nunulk
- Olaf Sebelin
- OldhamMade
- Oleh
- Oliver Medhurst
- Oliver Tosky
- ollie
- Optizio
- P.
- Patrick Wheeler
- Pattadon Sa-ngasri
- Paul Guse
- Pedro Correa
- Pete Jodo
- Peter Rice
- Peter Saxton
- Philpax
- Qdentity
- Race
- Ram Prasanth Udhaya Baskar
- Rasmus
- Raúl Chouza
- Redmar Kerkhoff
- Reilly Tucker Siemens
- Renato Massaro
- Renovator
- Richard Viney
- Rico Leuthold
- Rintaro Okamura
- Ripta Pasay
- rob durst
- Robert Attard
- Robert Ellen
- Robert Malko
- Rodrigo Álvarez
- Rohan
- Rotabull
- Rupus Reinefjord
- Ruslan Ustitc
- Russell Clarey
- Ryan Moore
- Sakari Bergen
- Sam Aaron
- Sam Zanca
- Sammy Isseyegh
- Samuel Cristobal
- Savva
- Saša Jurić
- Scott Trinh
- Scott Wey
- Scott Zhu Reeves
- Sean Cribbs
- Sean Roberts
- Sebastian Porto
- Seve Salazar
- Sgregory42
- Shane Poppleton
- Shawn Drape
- Shritesh Bhattarai
- shxdow
- Sigma
- simone
- Stefan
- Steinar Eliassen
- Stephane Rangaya
- Strandinator
- Sławomir Ehlert
- The Sentience Company
- Thomas
- Thomas Coopman
- Thomas Crescenzi
- Tim Brown
- Timo Sulg
- Tobias Ammann
- ToddG
- Tolek
- Tom Schuster
- Tomasz Kowal
- tommaisey
- Tristan de Cacqueray
- Tristan Sloughter
- Tudor Luca
- uh-kay
- upsidedowncake
- Valerio Viperino
- Vassiliy Kuzenkov
- vemolista
- Viv Verner
- Vojtěch Křižan
- Volker Rabe
- vshakitskiy
- vyacheslavhere
- Walton Hoops
- Will Ramirez
- Wilson Silva
- Wundersmiths
- Yamen Sader
- Yasuo Higano
- yoshie
- Zsolt Kreisz
- zwubs
- ~1814730
- ~1847917
- ~1867501