Gleam v0.5 released!

Gleam has reached v0.5! It’s quite a bit larger than any of the previous releases, so let’s take a look.

Labelled arguments

When functions take several arguments it can be difficult for the user to remember what the arguments are, and what order they are expected in.

To help with this Gleam supports labelled arguments, where function arguments are given an external label in addition to their internal name.

Take this function that replaces sections of a string:

pub fn replace(string, pattern, replacement) {
  // ...
}

It can be given labels like so.

pub fn replace(in string, each pattern, with replacement) {
  // The variables `string`, `pattern`, and `replacement` are in scope here
}

These labels can then be used when calling the function.

replace(in: "A,B,C", each: ",", with: " ")

// Labelled arguments can be given in any order
replace(each: ",", with: " ", in: "A,B,C")

// Arguments can still be given in a positional fashion
replace("A,B,C", ",", " ")

The use of argument labels can allow a function to be called in an expressive, sentence-like manner, while still providing a function body that is readable and clear in intent.

One neat thing is that unlike proplist arguments in Erlang or Elixir, Gleam’s labelled arguments are resolved entirely at compile time and have no performance or memory cost at runtime.

A more familiar syntax

One bit of common feedback was that the syntax for enums, case expressions, and blocks felt a little strange or not in keeping with the wider language.

In response to this we have adopted a syntax closer to that found in C-influenced languages such as ECMAScript.

case x {
  1 -> "It's one"
  2 -> "It's two"

  // Braces can be used for multiple statements
  _ -> {
    let number = int.to_string(x)
    string.concat(["I'm not sure what ", number, " is"])
  }
}
pub enum Cardinal {
  North
  East
  South
  West
}

Multi-subject case

Often it is useful to pattern match on multiple values at the same time. Previously in Gleam the solution was to wrap the values in a struct and pattern match on all values at once.

case Pair(x, y) {
  Pair(1, 1) -> "both are 1"
  Pair(1, _) -> "x is 1"
  Pair(_, 1) -> "y is 1"
  Pair(_, _) -> "neither are 1"
}

To remove some boilerplate here Gleam’s case expression now supports pattern matching on multiple values.

case x, y {
  1, 1 -> "both are 1"
  1, _ -> "x is 1"
  _, 1 -> "y is 1"
  _, _ -> "neither are 1"
}

Theoretically a case expression can match on up to 16,777,215 values. If you need to match on more values than this please let me know because wow, I wanna know what your code is doing.

Record compatibility

Gleam’s structs are Erlang tuples at runtime. This is great for performance and memory usage but makes constructing them from Erlang or Elixir less straightforward than if they were maps.

To resolve this Gleam’s structs now at runtime have a tag atom in their first position, making them compatible with Erlang records. An Erlang record definition header file is generated for each struct which can be imported into an Erlang project or used from Elixir using the Record module.

// src/cat.gleam

pub struct Cat {
  name: String
  is_cute: Bool
}

pub fn main() {
  Cat(name: "Nubi", is_cute: True)
}
%%% src/program.erl

-module(program).
-export([main/0]).

%% Import the Cat record definition
-import("gen/src/cat_Cat.hrl").

%% Use the Cat record
main() ->
    #cat{name = <<"Nubi">>, is_cute = true}.
# lib/program.ex

defmodule Program do
  # Import the Cat record definition
  require Record
  Record.defrecord(:cat, Record.extract(:cat, from: "gen/src/cat_Cat.hrl"))

  # Use the Cat record
  def main do
    cat(name: "Nubi", is_cute: true)
  end
end

Anonymous structs

One drawback to structs now being Erlang record compatible is that they can no longer be used to interop with Erlang tuples that do not have a tag atom in the first position.

Anonymous structs map 1 to 1 onto Erlang tuples can are useful for any situation where interop with Erlang tuples is required.

fn main() {
  struct(10, "hello") // Type is struct(Int, String)
  struct(1, 4.2, [0]) // Type is struct(Int, Float, List(Int))
}

Anonymous structs don’t need to be declared up front but also don’t have names for their fields, so for clarity prefer named structs for when you have more than 2 or 3 fields.

Unqualified imports

Tired of typing module names repeatedly for imported types and values? Well now you don’t have to.

import animal/cat.{Cat, stroke}

pub fn main() {
  let kitty = Cat(name: "Nubi", is_cute: True)
  stroke(kitty)
}

Project creation

The terminal command gleam new now accepts a --template flag to generate different styles of project. An OTP application template has been added alongside the existing OTP library template.

gleam new my_fantastic_application --template app

In addition all projects generated by gleam new come with the required configuration for CI on GitHub Actions. Push the project to GitHub and your code will be automatically compiled and your tests run on every code update.

To enable this we’ve created two new GitHub Actions for setting up Erlang and Gleam in your GitHub workflow.

The rest

As per usual, in addition to these features there’s been a number of other improvements to the quality of the error messages, the generated code, and a smattering of bug fixes. 😊

If you want to try out the new version of Gleam head over to the installation page. I’d love to hear how you find it and get your feedback so Gleam can continue to improve.

Want to view some existing Gleam projects? Head on over to the awesome-gleam list. Looking for something to build in Gleam? Check out the suggestions tracker.

Code Mesh 2019

Last month I got to speak about Gleam at my favourite conference Code Mesh! A recording of the talk can found on their YouTube channel:

Thanks

Lastly, a huge thank you to the contributors to and sponsors of Gleam since last release!

If you would like to help make strongly typed programming on the Erlang virtual machine a production-ready reality please consider sponsoring Gleam via the GitHub Sponsors program.

Thank you! 💜