Gleam is a type-safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.5.0 has been published, featuring lots of really nice developer experience and productivity improvements, including several useful language server code actions. Let’s take a look.

Before we start I just want to give extra thanks to Lambda, Gleam’s new primary sponsor. Gleam is an entirely open-source community driven project rather than being owned by any particular corporation or academic institution. All funding comes from sponsors, both corporate sponsors such as Lambda, and individuals sponsoring a few dollars a month on GitHub Sponsors. Thank you for making Gleam possible.

Context aware exhaustiveness errors and code action

The compile time error messages for inexhaustive pattern matching have been upgraded to show the unmatched values using the syntax the programmer would use in their code, respecting the aliases and imports in that module. For example, if you had this code:

import gleam/option

pub fn main() {
  let an_option = option.Some("wibble!")
  case an_option {
    option.None -> "missing"
  }
}

The error message would show the qualified option.Some(_) as the missing pattern:

error: Inexhaustive patterns
  ┌─ /root/prova/src/prova.gleam:5:3
  │
5 │ ╭   case an_option {
6 │ │     option.None -> "missing"
7 │ │   }
  │ ╰───^

This case expression does not have a pattern for all possible values. If it
is run on one of the values without a pattern then it will crash.

The missing patterns are:

    option.Some(_)

This makes it easier to understand the error message, and the missing patterns can be copied from the error directly into the source code.

Further still, when there is one of these errors in the code the language server offers a code action to add the missing patterns for you.

// Before
pub fn run(a: Bool) -> Nil {
  case a {}
}
// After
pub fn run(a: Bool) -> Nil {
  case a {
    False -> todo
    True -> todo
  }
}

Thank you Surya Rose for this! The code action is a real favourite of mine! I have been using it constantly.

Silent compilation

When you run a command like gleam run or gleam test it prints some progress information.

$ gleam run
 Compiling thoas
 Compiling gleam_json
 Compiling app
  Compiled in 1.67s
   Running app_test.main
Hello, world!

This is generally nice, but sometimes you only want to see the output from your tests or your program, so a --no-print-progress flag has been added to silence this additional output.

$ gleam run --no-print-progress
Hello, world!

Additionally, this information is now printed to standard error rather than standard out, making it possible to redirect it elsewhere in your command line shell.

Thank you Ankit Goel and Victor Kobinski for this!

Run dependency command any time

The gleam run command accepts a --module flag, which can be used to run a main function from any module in your project, including modules in your dependencies. It works by compiling your project, booting the virtual machine, and running the specified module.

The problem with this is that if your code doesn’t compile you won’t be able to run modules in your dependencies, even if they have already compiled successfully. You would have to fix your code before you can run anything.

The build tool now skips compiling your code if you’re running a dependency module, removing this limitation and adding a slight performance improvement as less work is being done. Thank you Giacomo Cavalieri!

Prettier HTML documentation

The gleam docs build command can be used to generate lovely searchable documentation for your code and the documentation comments within, and when you publish a package with gleam publish then the HTML documentation is also published for you.

Jiangda Wang has improved the styling of the sidebar of this documentation. Now if you have a project with long module names (such as my_project/web/api/v2/permissions/bucket_creation) the text will wrap at a / and the continued text on the next line will be indented, making it easier to read. Thank you Jiangda!

Prettier runtime errors

We’ve put a lot of work into making Gleam’s compile time errors as clear and understandable as possible, but when a runtime error crashes a program we’ve used the virtual machine’s default exception printing functionality, which wasn’t as clear as it could have been.

runtime error: let assert

Pattern match failed, no pattern matched the value.

unmatched value:
  Error("User not logged in")

stacktrace:
  my_app.-main/0-fun-0- /root/my_app/src/my_app.gleam:8
  gleam/list.do_map /root/my_app/build/packages/gleam_stdlib/src/gleam/list.gleam:380
  my_app.-main/0-fun-1- /root/my_app/src/my_app.gleam:11

The virtual machine doesn’t have a source-maps feature, so the line numbers may not be perfectly accurate, but the generated code now includes per-function annotations to improve the accuracy compared to previous versions. Additionally, OTP application trees are now shut down gracefully when main exits.

Gleam requirement detection

In a package’s gleam.toml you can specify a minimum required Gleam version. This is useful as if someone attempts to compile your package with too low a version they will be presented with a clear error message instead of a cryptic syntax error. Previously it was up for the programmer to keep this requirement accurate for the code, which is error prone and rarely done.

The compiler can now infer the minimum Gleam version needed for your code to compile and emits a warning if the project’s gleam version constraint doesn’t include it. For example, let’s say your gleam.toml has the constraint gleam = ">= 1.1.0" and your code is using some feature introduced in a later version:

// Concatenating constant strings was introduced in v1.4.0!
pub const greeting = "hello " <> "world!"

You would now get the following warning:

warning: Incompatible gleam version range
  ┌─ /root/datalog/src/datalog.gleam:1:22
  │
1 │ pub const greeting = "hello " <> "world!"
  │                      ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.4.0

Constant strings concatenation was introduced in version v1.4.0. But the
Gleam version range specified in your `gleam.toml` would allow this code to
run on an earlier version like v1.1.0, resulting in compilation errors!
Hint: Remove the version constraint from your `gleam.toml` or update it to be:

    gleam = ">= 1.4.0"

Running the gleam fix will now update the package’s gleam version constraint for you automatically.

If a package has an incorrect constraint then gleam publish will not let the package be published until it is correct. If there’s no explicit constraint in the gleam.toml file then the inferred requirement will be added to the package in the package repository for its users to benefit from.

Thank you Giacomo Cavalieri for these features!

Bit array analysis improvements

Gleam’s bit array syntax allows you to construct and parse binary data in a way that may be easier to understand than using binary operators. Giacomo Cavalieri has introduced some overloading to the syntax so literal unicode segments no longer need to be annotated with utf8.

<<"Hello", 0:size(1), "world">>

Is the same as:

<<"Hello":utf8, 0:size(1), "world":utf8>>

On JavaScript bit arrays currently have to be byte aligned. Previously invalid alignment would be a runtime error, but Richard Viney has added analysis that turns this into a compiler error instead.

Thank you Giacomo and Richard!

Context aware function inference

Anonymous functions that are immediately called with a record or a tuple as an argument are now inferred correctly without the need to add type annotations. For example you can now write:

fn(x) { x.0 }(#(1, 2))
// ^ you no longer need to annotate this!

This also includes to anonymous functions in pipelines, making it easy to access a record field without extra annotations or assigning to a variable.

pub type User {
  User(name: String)
}

pub fn main() {
  User("Lucy")
  |> fn(user) { user.name }
  //    ^^^^ you no longer need to annotate this!
  |> io.debug
}

Thank you sobolevn for these type inference improvements!

Helpful errors for using modules as values

Modules and values occupy two different namespaces in Gleam, so you can use the same name for a variable and an import and the compiler will pick whichever is correct for that context.

Other BEAM languages such as Erlang and Elixir have modules that can be assigned to variables and passed around as values, so sometimes folks new to Gleam can be confused and try to use a module as a value. The compiler now has a helpful error message specifically for this case.

import gleam/list

pub fn main() {
  list
}
error: Module `list` used as a value
  ┌─ /root/prova/src/prova.gleam:4:3
  │
4 │   list
  │   ^^^^

Modules are not values, so you cannot assign them to variables, pass them to
functions, or anything else that you would do with a value.

Thank you sobolevn!

Helpful errors for OOP-ish syntax errors

Another common mistake is attempting to write an OOP class in Gleam. Being a functional language Gleam doesn’t have classes or methods, only data and functions. A helpful error message has been added for when someone attempts to define methods within a custom type definition.

pub type User {
  User(name: String)

  fn greet(user: User) -> String {
    "hello " <> user.name
  }
}
error: Syntax error
  ┌─ /root/prova/src/prova.gleam:8:3
  │
8 │   fn greet(user: User) -> String {
  │   ^^ I was not expecting this

Found the keyword `fn`, expected one of:
- `}`
- a record constructor
Hint: Gleam is not an object oriented programming language so
functions are declared separately from types.

Thank you sobolevn!

Missing import suggestions

If some code attempts to use a module that has not been imported yet then the compile error will suggest which module you may want to import to fix the problem. This suggestion checks not only the name of the module but also whether it contains the value you are attempting to access. For example, this code results in this error with a suggestion:

pub fn main() {
  io.println("Hello, world!")
}
error: Unknown module
  ┌─ /src/file.gleam:2:3
  │
2 │   io.println("Hello, world!")
  │   ^^

No module has been found with the name `io`.
Hint: Did you mean to import `gleam/io`?

However this code does not get a suggestion in its error message:

pub fn main() {
  io.non_existent()
}

When there is a suitable module to suggest the language server offers a code action to add an import for that module to the top of the file.

pub fn main() {
  io.println("Hello, world!")
}
// After
import gleam/io

pub fn main() {
  io.println("Hello, world!")
}

Thank you Surya Rose for this!

Helpful invalid external target errors

Jiangda Wang has added a helpful error message for when the specified target for an external function is unknown. Thank you Jiangda!

error: Syntax error
  ┌─ /root/my_app/src/my_app.gleam:5:1
  │
5 │ @external(elixir, "main", "run")
  │ ^^^^^^^^^ I don't recognise this target

Try `erlang`, `javascript`.

Helpful “if” expression errors

Gleam has one single flow-control construct, the pattern matching case expression. The compiler now shows an helpful error message if you try writing an if expression instead of a case. For example, this code:

pub fn main() {
  let a = if wibble {
    1
  }
}
error: Syntax error
  ┌─ /src/parse/error.gleam:3:11
  │
3 │   let a = if wibble {
  │           ^^ Gleam doesn't have if expressions

If you want to write a conditional expression you can use a `case`:

    case condition {
      True -> todo
      False -> todo
    }

See: https://tour.gleam.run/flow-control/case-expressions/

Thank you Giacomo Cavalieri!

Implicit todo formatting

If you write a use expression without any more code in that block then the compiler implicitly inserts a todo expression. With this release the Gleam code formatter will insert that todo for you, to make it clearer what is happening.

// Before
pub fn main() {
  use user <- result.try(fetch_user())
}
// After
pub fn main() {
  use user <- result.try(fetch_user())
  todo
}

Thank you to our formatter magician Giacomo Cavalieri!

Result discarding code action

The compiler will warn if a function returns a Result and that result value is not used in any way, meaning that the function could have failed and the code doesn’t handle that failure.

Jiangda Wang has added a language server code action to assign this unused result to _ for times when you definitely do not care if the function succeeded or not. Thank you!

// Before
pub fn main() {
  function_which_can_fail()
  io.println("Done!")
}
// After
pub fn main() {
  let _ = function_which_can_fail()
  io.println("Done!")
}

Variable and argument completion

And last but not least, Ezekiel Grosfeld has added autocompletion for local variable and function arguments to the language server, an addition that people have been excitedly asking for for a long time. Thank you Ezekiel!

Bug bashing

An extra special shout-out to the bug hunters Ankit Goel, Giacomo Cavalieri, Gustavo Inacio, Surya Rose, and Victor Kobinski Thank you!

If you’d like to see all the changes for this release, including all the bug fixes, check out the changelog in the git repository.

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. I currently earn substantially less than the median salary tech lead salary for London UK, the city in which I live, and Gleam is my sole source of income.

If you appreciate Gleam, please support the project on GitHub Sponsors with any amount you comfortably can. I am endlessly grateful for your support, thank you so much.

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

Try Gleam