Gleam is a type safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.18.0 has been published.
Language server record field support
Gleam's focus on static analysis and static types really helps with creating tooling, and that can be seen very clearly with Gleam's much-loved language server. All its features and capabilities can be seen in its documentation.
One remaining missing part of it was full support for record fields, a problem
now fixed! The language server now supports go-to-definition, find-references
and rename for record fields. These work on the field declaration, on labelled
arguments, on labelled patterns, on record updates and on record.field
accesses, both within a module and across modules. For example:
pub type Person {
Person(name: String, age: Int)
}
pub fn main() {
let lucy = Person(name: "Lucy", age: 10)
lucy.name
}
Thank you Alistair Smith! People will be very happy for this one!
Type variable renaming
Similarly, the language server now permits renaming type variables in functions, types, and constants. For example:
pub fn twice(value: a, f: fn(a) -> a) -> a {
f(f(value))
}
Produces:
pub fn twice(value: anything, f: fn(anything) -> anything) -> anything {
f(f(value))
}
Thank you Surya Rose!
Updating imports for renamed modules
Another addition to the language server is support for renaming modules. When your text editor tells the language server that a Gleam file has been renamed, it will find all the uses of that module and update them for the new name. For example:
import db_users
pub fn main() -> db_users.User {
db_users.new("username")
}
Renaming db_users.gleam to database/user.gleam would produce:
import database/user
pub fn main() -> user.User {
user.new("username")
}
Thank you Surya Rose!
Pattern match on value code action
Flow control in Gleam is done with pattern matching. With how common this is,
the language server's "pattern match on value" code action is a great
time-saver, quickly inserting a case expression that the programmer can add
their logic to.
The code action can now be triggered on function calls and their returned values. For example:
pub fn main() {
load_user()
}
fn load_user() -> Result(User, Nil) { todo }
Will produce the following code:
pub fn main() {
case load_user() {
Ok(value) -> todo
Error(value) -> todo
}
}
Thank you Giacomo Cavalieri!
Faster JavaScript with data singletons
When targeting JavaScript, Gleam's compiler outputs straightforward JavaScript code, similar to what a human would write. This gives it performance similar to hand-written JavaScript, which the web framework Lustre has used to achieve comparable or better performance to JavaScript frameworks, such as React.
That said, there is always opportunity to improve the code generated by a compiler. Gleam will now identify data structures where all instances are equivalent, and use a single value for each use instead of constructing a new instance each time.
Take this Gleam code, for example:
import gleam/option.{type Option}
pub fn two_optionals() -> #(Option(a), Option(b)) {
#(option.None, option.None)
}
You could imagine that previously, that Gleam code would compile to JavaScript code like this (pseudocode):
import { None } "../gleam_stdlib/gleam/option.mjs";
export function two_optionals() {
return [None.new(), None.new()];
}
While now code closer to this will be generated (pseudocode):
import { None } "../gleam_stdlib/gleam/option.mjs";
export function two_optionals() {
return [None.instance, None.instance];
}
In our tests this has an impactful improvement to performance, especially to
code that uses lots of these data structures such as the view functions of
Lustre applications.
Thank you Surya Rose for this optimisation!
Ambiguous pipe syntax deprecation
Gleam's much-loved pipe syntax gives programmers another way to write nested function calls so that they read top-to-bottom and left-to-right.
The pipeline one |> two is equivalent to two(one). Most of the time one |> two(three)
is equivalent to two(one, three), however if that doesn't result in the
correct types then it can instead compile to two(three)(one). This second
option means that the pipe syntax is visually ambiguous, and one cannot tell
exactly how it compiles without knowing about the function being called. This
is not in keeping with the wider language, so the two(three)(one) version has
been deprecated and will need to be written explicitly as one |> two(three)().
With this, pipelines have the same rules as use expressions, and the reader
can always tell how they compile.
We scanned all the publicly published Gleam code and found that the deprecated style is almost never used, but just to help anyone who is using it, the language server now offers a code action to fix the new deprecated style.
Thank you Surya Rose!
Git dependency path support
Gleam's build tool supports adding dependencies from git repositories, which is often useful if there is a package that is not ready to be published, but you still want to try it out. The build tool didn't let you specify a path within the git repository, so if the package you want to use isn't at the root of the repository then you were out-of-luck.
To resolve this John Downey has added
an optional path field for git dependencies, to specify a subdirectory within
the repository.
[dependencies]
my_package = {
git = "https://github.com/example/monorepo",
ref = "main",
path = "packages/my_package",
}
Thank you John! Monorepo fans rejoice!
Raising Hex API limits
Gleam uses Hex, the package manager for the Erlang ecosystem. It has rate
limits that will not be hit in typical use, but can cause problems for folks
doing many chatty operations, such as running gleam deps update on many
packages within a monorepo.
Hex offers much higher rate limits for authenticated requests, so an API key
can now be provided to Gleam via the HEXPM_READ_API_KEY environment variable.
This will only be used for read operations, so we recommend using an API key
that only has read permissions.
Thank you John Downey!
Hex experience improvements
That's not all for Hex, we've also fixed a bunch of sharp edges in the Gleam developer experience when working with the package manager.
Moritz Böhme has added a helpful error message for when someone attempts to revert a release that is older than 24 hours and so cannot be reverted. Previously the error from the API would be shown, which could be confusing.
He has also improved the error message when failing to decrypt a locally stored Hex API key, such as due to making a typo in the decryption password. Thank you Moritz!
Surya Rose has updated the Gleam build
tool to use the new Hexdocs URLs, as they recently switched from
hexdocs.pm/package_name to package-name.hexdocs.pm for security reasons.
Lastly, the gleam hex owner transfer command now takes the username via the
flag --user, and the gleam hex owner add command now takes the package name
via the flag --package. This should make these commands a little easier to
understand.
Convert int base code action
Like many languages, Gleam supports writing ints in decimal, octal, hexadecimal, and binary. The language server now offers code actions to switch between them instantly. For example:
pub fn lucky_number() {
0b1011
}
The language server is going to show code actions to rewrite it as 11,
0o13, or 0xB. Thank you Giacomo Cavalieri!
Generate missing type code action
The language server now offers a code action to generate a missing type
definition when an unknown type is referenced. For example, if Wibble
is not defined:
pub fn run(data: Wibble(Int)) { todo }
The code action will generate:
pub type Wibble(a)
pub fn run(data: Wibble(Int)) { todo }
Thank you Daniele Scaratti!
In addition to regular double-slash comments, Gleam has triple and quadruple slash comments which are used for writing API documentation. The language server now has code actions to convert between regular comments and documentation comments. For example:
pub fn wibble() {
todo
}
pub fn wobble () {
todo
}
Triggering the code actions in all of these places will result in:
pub fn wibble() {
todo
}
pub fn wobble () {
todo
}
Thank you Daniel Venable!
Redundant alias fixing
Gleam's automatic code formatter will now remove redundant import aliases that have no effect on the code. For example:
import wibble.{Wibble as Wibble}
The name and the alias of the imported value are both Wibble, so the
formatter will remove the alias like so:
import wibble.{Wibble}
Thank you Daniel Venable!
Faster compilation with arenas
But wait, where is Giacomo Cavalieri? Normally there are many changes with his name next to them in a Gleam release post… Well he has been busy making large changes to compiler internals, namely introducing memory arenas. He has gone into detail about this on his blog, so I've let him explain there.
The user-facing outcome of this is improved performance of the code generators
and the formatter. gleam format has been measured to be up to 13% faster on
projects like lustre, with a 10% smaller peak memory footprint!
This work is still ongoing, and we should have some very exciting additions in future releases.
And more!
And a huge thank you to the bug fixers and experience polishers:
0xda157, Andrey Kozhev, Asish Kumar, Charlie Tonneslan, Eyup Can Akman, Francesco Cappetti, Gavin Morrow, Giacomo Cavalieri, John Downey, Matt Champagne, Surya Rose, and Zbyněk Juřica.
For full details of the many fixes and improvements they've implemented, see the changelog.
Support Gleam
Gleam is not from big-tech and has not taken any VC funding. We rely entirely on the community for our livelihoods.
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 contributors! And special thanks to our top sponsor: