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 comm