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
  //   ^ Go-to-definition jumps to the `name` field, and renaming it here
  //     renames the field everywhere it is used.
}

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 {
  //                ^ Rename to "anything"
  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()
// ^^ Triggering the code action over here
}

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
//^^^^^^ Hovering this
}

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!

Comment conversion code actions

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:

// Module description.
// Code action available here.

// Comment before function.
// Another code action here.
pub fn wibble() {
  // No code action here.
  todo
}

/// Doc comment.
/// Another code action here.
pub fn wobble () {
  todo
}

Triggering the code actions in all of these places will result in:

//// Module description.
//// Code action available here.

/// Comment before function.
/// Another code action here.
pub fn wibble() {
  // No code action here.
  todo
}

// Doc comment.
// Another code action here.
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:

Try Gleam