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!

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.

Thanks for reading, I hope you have fun with Gleam! 💜

Try Gleam