Gleam

Gleam is a statically typed functional programming language for building scalable concurrent systems.

It compiles to Erlang and has straightforward interop with other BEAM languages such as Erlang, Elixir and LFE.

It looks like this:

pub enum Tree =
  | Leaf(Int)
  | Node(Tree, Tree)

pub fn any(tree, predicate) {
  case tree {
  | Leaf(i) -> predicate(i)
  | Node(left, right) -> any(left, predicate) || any(right, predicate)
  }
}

pub fn has_even_leaf(tree) {
  any(tree, fn(i) {
    i % 2 == 0
  })
}

The source code can be found at https://github.com/lpil/gleam.

For Gleam chat we have the IRC channel #gleam-lang on Freenode.

Principles

Be safe

An expressive type system inspired by the ML family of languages helps us find and prevent bugs at compile time, long before it reaches your users.

For the problems the type system can't solve (such as your server being hit by a bolt of lightning) the Erlang/OTP runtime provides well tested mechanisms for gracefully handling failure.

Be friendly

Hunting down bugs can be stressful so feedback from the compiler should be as clear and helpful as possible. We want to spend more time working on our application and less time looking for typos or deciphering cryptic error messages.

As a community we want to be friendly too. People of all backgrounds, genders, and experience levels are welcome and must receive equal respect.

Be performant

The Erlang/OTP runtime is known for its speed and ability to scale, enabling organisations such as WhatsApp and Ericsson to reliably handle massive amounts of traffic at low latency. Gleam should take full advantage of this runtime and be as fast as other BEAM languages such as Erlang and Elixir.

Be a good citizen

Gleam makes it easy to use code written in other BEAM languages such as Erlang, Elixir and LFE, so there's a rich ecosystem of tools and library for Gleam users to make use of.

Users of other BEAM languages should in return be able to take advantage of Gleam, either by transparently making use of libraries written in Gleam, or by adding Gleam modules to their existing project with minimal fuss.

Getting started

In this chapter we get the Gleam language set up on your computer and learn how to create an navigate a Gleam project.

Good luck, have fun!

Installing Gleam

Precompiled for Linux or macOS

The easiest way to install Gleam on Linux and Apple macOS is to download a prebuilt version of the compiler from the GitHub release page.

asdf version manager

asdf is a tool for installing and managing multiple version of programming languages at the same time. Install the asdf-gleam plugin to manage Gleam with asdf.

Arch Linux

Gleam is available through the Arch User Repository as package gleam. You can use your prefered helper to install it or clone it for manual build from https://aur.archlinux.org/gleam.git.

Build from source

The compiler is written in the Rust programming language and so if you wish to build Gleam from source you will need to install the Rust compiler.

# Download the Gleam source code git repository
cd /tmp
git clone https://github.com/lpil/gleam.git --branch v0.2.0
cd gleam

# Build the Gleam compiler. This will take some time!
make install

# Verify the compiler is installed
# Prints "gleam $VERSION"
gleam --version

Installing Erlang

Gleam compiles to Erlang code, so Erlang needs to be installed to run Gleam code.

Precompiled builds for many popular operating systems can be downloaded from the Erlang solutions website,

Guides for installing Erlang on specific operating systems can be found below, as well as information on installing multiple versions of Erlang at once using version manager tools.

Once Erlang has been installed you can check it is working by typing erl -version in your computer's terminal. You will see version information like this if all is well:

$ erl -version
Erlang (SMP,ASYNC_THREADS,HIPE) (BEAM) emulator version 10.1

Linux

Debian Linux

sudo apt-get update
sudo apt-get install erlang

Ubuntu Linux

sudo apt-get update
sudo apt-get install erlang

Mac OS X

Using Homebrew

With Homebrew installed run the following:

brew update
brew install erlang

Windows

Using Chocolatey

With Chocolatey installed on your computer run the following:

choco install erlang

Using version managers

asdf

The asdf version manager has a plugin for installing Erlang. Installation and usage instructions can be found here:

Editor support

Gleam plugins are available for several popular editors. If one exists for your editor of choice consider installing it for syntax highlighting and other niceties.

Creating a project

Installing the rebar3 build tool

Note: Gleam's tooling is very young and in a state of flux. Expect rough edges and breaking changes to come.

The Gleam compiler can build Gleam projects that are managed with the standard Erlang build tool, rebar3. If you don't have rebar3 installed please install it now and the rebar_gleam plugin.

Generating a project

Now a project can be generated using rebar3 like so:

rebar3 new gleam_lib my_fantastic_project
cd my_fantastic_project

You'll now have a project with this structure:

.
├── gleam.toml
├── LICENSE
├── README.md
├── rebar.config
├── src
│   ├── my_fantastic_project.app.src
│   └── my_fantastic_project.gleam
└── test
    └── my_fantastic_project_test.gleam

2 directories, 7 files

Here are some commonly used commands rebar3 commands that you can use with your new project:

# Run an interactive shell with your code loaded (Erlang syntax)
rebar3 shell

# Run the eunit tests
rebar3 eunit

More information can be found on the rebar3 documentation website.

What next?

Want to see some Gleam code? See the example projects.

Looking learn the language? Check out the language tour.

Need ideas for a project? We have a list of libraries that need writing.

Example projects

When learning a new language it can often be used to have example code to refer to and learn from, so here's some examples:

Tiny: URL shortener

A simple HTML serving web application that takes URLs and gives the user a shorter URL to use in its place.

Uses the Elli web server and the Postgresql database via the epgsql client.

The Gleam standard library

A collection of modules for working with the common data structures of Gleam. Makes heavy use of Erlang FFI.

Language Tour

In this chapter we explore the fundamentals of the Gleam language, namely its syntax, core data structures, flow control features, and static type system.

After completion the reader should know enough to start reading and writing Gleam code, assuming they have some prior programming experience.

In some section we touch on the runtime representation of various features. This is useful for programmers with Erlang or Elixir experience who wish to use Gleam alongside these languages. If you are using Gleam alone this information can be safely ignored.

Comments

Gleam allows you to write comments in your code.

Here’s a simple comment:

// Hello, world!

In Gleam, comments must start with two slashes and continue until the end of the line. For comments that extend beyond a single line, you’ll need to include // on each line, like this:

// Hello, world! I have a lot to say, so much that it will take multiple
// lines of text. Therefore, I will start each line with // to denote it
// as part of a multi-line comment.

Comments can also be placed at the end of lines containing code:

pub fn add(x, y) {
  x + y // here we are adding two values together
}

Comments may also be indented:


pub fn multiply(x, y) {
  // here were are multiplying x by y
  x * y 
}

String

Gleam's has UTF-8 binary strings, written as text surrounded by double quotes.

"Hello, Gleam!"

Strings can span multiple lines.

"Hello
Gleam!"

Special characters such as " need to be escaped with a \ character.

"Here is a double quote -> \" <-"

Bool

A Bool can be either True or False.

Gleam defines a handful of operators that work with Bools.

False && False // => False
False && True  // => False
True && False  // => False
True && True   // => True

False || False // => False
False || True  // => True
True || False  // => True
True || True   // => True

&& and || are short circuiting, meaning they don't evaluate the right hand side if they don't have to.

&& evaluates the right hand side if the left hand side is True.

|| evaluates the right hand side if the left hand side is False.

Erlang interop

While written in the code using a capital letter, they are represented at runtime with the atoms true and false, making them compatible with Elixir and Erlang's booleans.

This is important if you want to use Gleam and Elixir or Erlang together in one project.

// Gleam
True
False
% Erlang
true.
false.

Int and Float

Gleam's main number types are Int and Float.

Ints

Ints are "whole" numbers.

1
2
-3
4001

Gleam has several operators that work with Ints, all of which return Ints.

1 + 1 // => 2
5 - 1 // => 4
5 / 2 // => 2
3 * 3 // => 9
5 % 2 // => 1

Floats

Floats are numbers that have a decimal point.

1.5
2.0
-0.1

Floats also have their own set of operators.

1.0 +. 1.4 // => 2.4
5.0 -. 1.5 // => 3.5
5.0 /. 2.0 // => 2.5
3.0 *. 3.1 // => 9.3

Let bindings

A value can be given a name using let. Names can be reused by later let bindings, but the values contained are immutable, meaning the values themselves cannot be changed.

let x = 1
let y = x
let x = 2

x  // => 2
y  // => 1

Tuples

Tuples are an ordered collection of elements of a fixed size. Each element can be of a different type.

{"Cat", True}  // Type {String, Bool}
{1, 2.0, "3"}  // Type {Int, Float, String}

Contained values can be extracted from tuples using a let binding.

let values = {1, 2.0}
let {x, y} = values

x  // => 1
y  // => 2.0

List

Lists are ordered collections of values. They're one of the most common data structures in Gleam.

Lists are homogeneous, meaning all the elements of a List must be of the same type. Attempting to construct a list of multiple types of element will result in the compiler presenting a type error.

[1, 2, 3, 4]  // List(Int)
[1.22, 2.30]  // List(Float)
[1.22, 3, 4]  // Type error!

Prepending to a list is very fast, and is the preferred way to add new values.

[1 | [2, 3]]  // => [1, 2, 3]

Note that as all data structures in Gleam are immutable so prepending to a list does not change the original list, instead it efficiently creates a new list with the new additional element.

let x = [2, 3]
let y = [1 | x]


x  // => [2, 3]
y  // => [1, 2, 3]

Case

The case expression is the most common kind of flow control in Gleam code. It allows us to say "if the data has this shape then do that", which we call pattern matching.

Here we match on an Int and return a specific string for the values 0, 1, and 2. The final pattern n matches any other value that did not match any of the previous patterns.

case some_number {
| 0 -> "Zero"
| 1 -> "One"
| 2 -> "Two"
| n -> "Some other number" // This matches anything
}

Pattern matching on a Bool value is the Gleam alternative to the if else statement found in other languages.

case some_bool {
| True -> "It's true!"
| False -> "It's not true."
}

Gleam's case is an expression, meaning it returns a value and can be used anywhere we would use a value. For example, we can name the value of a case expression with a let binding.

let description =
  case True {
  | True -> "It's true!"
  | False -> "It's not true."
  }

description  // => "It's true!"

Destructuring

Like let bindings a case expression can be used to destructure values that contain other values, such as tuples and lists.

case xs {
| [] -> "This list is empty"
| [a] -> "This list has 1 element"
| [a, b] -> "This list has 2 element"
| other -> "This list has more than 2 elements"
}

It's not just the top level data structure that can be pattern matches, contained values can also be matched. This gives case the ability to concisely express flow control that might be verbose without pattern matching.

case xs {
| [[]] -> "The only element is an empty list"
| [[] | _] -> "The 1st element is an empty list"
| [[4] | _] -> "The 1st element is a list of the number 4"
| other -> "Something else"
}
case xs {
| {1, _} -> "The 1st element is 1"
| {_, 1} -> "The 2nd element is 1"
| other -> "Something else"
}

Function

Named functions

Named functions in Gleam are defined using the pub fn keywords.

pub fn add(x, y) {
  x + y
}

pub fn multiply(x, y) {
  x * y
}

Functions in Gleam are first class values and so can be assigned to variables, passed to functions, or anything else you might do with any other data type.

// This function takes a function as an argument
pub fn twice(f, x) {
  f(f(x))
}

pub fn add_one(x) {
  x + 1
}

pub fn add_two(x) {
  twice(add_one, x)
}

Anonymous functions

Anonymous functions can be defined with a similar syntax.

pub fn run() {
  let add = fn(x, y) { x + y }

  add(1, 2)
}

Function capturing

There is a shorthand syntax for creating anonymous functions that take one argument and call another function. The _ is used to indicate where the argument should be passed.

pub fn add(x, y) {
  x + y
}

pub fn run() {
  let add_one = add(1, _)

  add_one(2)
}

The function capture syntax is often used with the pipe operator to create a series of transformations on some data.

pub fn add(x, y) {
  x + y
}

pub fn run() {
  // This is the same as add(add(add(1, 3), 6), 9)
  1
  |> add(_, 3)
  |> add(_, 6)
  |> add(_, 9)
}

Module

Gleam programs are made up of bundles of functions and types called modules. Each module has its own namespace and can export types and values to be used by other modules in the program.

// inside src/nasa/rocket_ship.gleam

fn count_down() {
  "3... 2... 1..."
}

fn blast_off() {
  "BOOM!"
}

pub fn launch() {
  [
    count_down(),
    blast_off(),
  ]
}

Here we can see a module named nasa/rocket_ship, the name determined by the filename src/nasa/rocket_ship.gleam. Typically all the modules for one project would live within a directory with the name of the project, such as nasa in this example.

For the functions count_down and blast_off we have omitted the pub keyword, so these functions are private module functions. They can only be called by other functions within the same module.

Import

To use functions or types from another module we need to import them using the import keyword.

// inside src/nasa/moon_base.gleam

import nasa/rocket_ship

pub fn explore_space() {
  rocket_ship:launch()
}

The statement import nasa/rocket_ship creates a new variable with the name rocket_ship and the value of the rocket_ship module.

In the explore_space function we call the imported module's public launch function using the : operator. If we had attempted to call count_down it would result in a compile time error as this function is private in the rocket_ship module.

Named import

It is also possible to give a module a custom name when importing it using the as keyword.

import unix/cat
import animal/cat as kitty

This may be useful to differentiate between multiple modules that would have the same default name when imported.

First class modules

Modules in Gleam are first class values and can be assigned to variables, passed to functions, or anything else that we can do with regular values.

import nasa/rocket_ship
import nasa/new_website
import nasa/navy_boat

pub fn perform_launch(some_module) {
  some_module:launch()
}

pub fn run() {
  perform_launch(rocket_ship)
  perform_launch(new_website)
  perform_launch(navy_boat)
}

Here we have define a function that takes a module as an argument and then called it with various different modules.

The perform_launch function doesn't care what module it takes, so long as it has a public function called launch that takes no arguments.

Map

Gleam's maps are a collection of names and values where each value can be of any type.

{
  name = "Rex",
  size = 40,
}

Accessing field values

Map values can be accessed using the map.field_name syntax.

let pup = { name = "Rex", size = 40 }
let name = pup.name

name  // => "Rex"

Inserting field values

A new map with updated or additional fields can be created using the { map | field_name = value } update syntax.

The updated map fields do not have to have the same types as the original fields.

let pup = { name = "Rex", size = 40 }
let dog = { pup | size = 70, playful = True }

pup  // { name = "Rex", size = 40 }
dog  // { name = "Rex", size = 70, playful = True }

Types

The type of a map depends upon the names and types of its fields.

{ name = "Rex" }             // Type { name = String }
{ name = "Rex", size = 40 }  // Type { name = String, size = Int }

The Gleam compiler keeps tracks of what fields and values each map has and will present a compile time error if you try to use a map field that does not exist or has the incorrect type.

let pup = { name = "Rex", size = 40 }

pup.address   // Compile time error! Unknown field
pup.name + 1  // Compile time error! Wrong type

Parameterised map fields

Gleam's type system aims to be as permissive when it comes to maps passed to functions. Take this function for example.

fn get_following_year(map) {
  map.year + 1
}

The type of this function is fn({ a | year = Int }) -> Int.

The a | in { a | year = Int } means "any other fields", so this function can be called with any map so long as the map has a year field that has an Int value.

let date = { day = 5, month: 1, year = 2019 }
let book = { title = "Sabriel", year = 1995 }
let soup = { kind = "Tomato", spicy = False }
let wine = { kind = "Fancy!", year = "Good" }

get_following_year(date)  // => 2020
get_following_year(book)  // => 1996
get_following_year(soup)  // Compile time error! No year field
get_following_year(wine)  // Compile time error! Wrong field type

Erlang interop

At runtime Gleam maps are Erlang maps with atom keys. They are similar to Elixir's structs, but do not need to be declared prior to being used.

Enums

Enums in Gleam are a way of modeling data that can be one of a few different variants. They must be declared before use, and the names of variants must be unique for the given module.

We've seen an enum already in this chapter- Bool.

Bool is defined like this:

// A Bool is a value that is either `True` or `False`
enum Bool =
  | True
  | False

Enum variants can also contain other values, and these values can be extracted using a let binding.

enum User =
  | LoggedIn(String)  // A logged in user with a name
  | Guest             // A guest user with no details
let sara = LoggedIn("Sara")
let rick = LoggedIn("Rick")
let visitor = Guest

Destructuring

When given an enum we can pattern match on it to determine which variant it is and to assign names to any contained values.

fn get_name(user) {
  case user {
  | LoggedIn(name) -> name
  | Guest -> "Guest user"
  }
}

Enums can also be destructured with a let binding.

enum Score =
  | Points(Int)
let score = Points(50)
let Points(p) = score

p // => 50

Erlang interop

At runtime enum variants with no contained values become atoms. The atoms are written in snake_case rather than CamelCase so LoggedIn becomes logged_in.

Enum variants with contained values become tuples with a tag atom.

// Gleam
Guest
LoggedIn("Kim")
# Elixir
:guest
{:logged_in, "Kim"}
% Erlang
guest.
{logged_in, <<"Kim">>}.

External function

Gleam is just one of many languages on the Erlang virtual machine and at times we may want to use functions from these other languages in our Gleam programs. To enable this Gleam allows the importing of external functions, which may be written in any BEAM language.

External functions are typically written in a different language with a different type system, so the compiler is unable to determine the type of the function and instead the programmer must inform the compiler the type.

Gleam trusts that the type given is correct so an inaccurate type annotation can result in unexpected behaviour and crashes at runtime. Be careful!

Example

The Erlang rand module has a function named uniform that takes no arguments and returns a Float.

The Elixir module IO has a function named inspect that takes any value, prints it, and returns the same value.

If we want to import these functions and use them in our program we would do so like this:

pub external fn random_float() -> Float = "rand" "uniform"

// Elixir modules start with `Elixir.`
pub external fn inspect(a) -> a = "Elixir.IO" "inspect"

External type

In addition to importing external functions we can also import external types. Gleam knows nothing about the runtime representation of these types and so they cannot be pattern matched on, but they can be used with external functions that know how to work with them.

Here is an example of importing a Queue data type and some functions from Erlang's queue module to work with the new Queue type.

pub external type Queue(a)

pub external fn new() -> Queue(a) = "queue" "new"

pub external fn length(Queue(a)) -> Int = "queue" "len"

pub external fn push(Queue(a), a) -> Queue(a) = "queue" "in"

FAQs

Why is the compiler written in Rust?

Prototype versions of the Gleam compiler was written in Erlang, but a switch was made to Rust as the lack of static types was making refactoring a slow and error prone process. A full Rust rewrite of the prototype resulted in the removal of a lot of tech debt and bugs, and the performance boost is nice too!

One day Gleam may have a compiler written in Gleam, but for now we are focused on developing other areas of the language such as libraries, tooling, and documentation.

Will Gleam have type classes?

Some form of ad-hoc polymorphism could be a good addition to the ergonomics of the language, though what shape that may take is unclear. Type classes are one option, OCaml style implicit modules are another, or perhaps it'll be something else entirely.

How is message passing typed?

Gleam doesn't currently have first class support for the BEAM's concurrency primitives such as receive, send, and spawn. This is because research is still ongoing as to the best way to apply a strong type system to them while still enabling established OTP patterns. For now these primitives should be used via the Erlang FFI, making them dynamically typed.

Many OTP patterns such as gen_server are functional in nature and don't require direct use of these primitives so these behaviours can be implemented in Gleam today.

How does Gleam compare to Alpaca?

Alpaca is similar to Gleam in that it is a statically typed language for the Erlang VM that is inspired by the ML family of languages. It's a wonderful project and we hope they are wildly successful!

Here's a non-exhaustive list of differences:

  • Alpaca functions are auto-curried, Gleam's are not.
  • Alpaca's unions can be untagged, with Gleam all variants in an enum need a name.
  • Alpaca's compiler is written in Erlang, Gleam's is written in Rust.
  • Alpaca's syntax is closer to ML family languages, Gleam's is closer to C family languages.
  • Alpaca compiles to Core Erlang, Gleam compiles to regular Erlang.

Alpaca is great, check it out! :)

Should I put Gleam in production?

Probably not. Gleam is a very young language and there may be all kinds of problems and breaking changes down the line.

Having said that, the Erlang VM is extremely mature and well tested, and if you decide to move away from Gleam the language you can compile your code to Erlang and maintain that in future.

Is it good?

Yes.