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(value) =
| Leaf(value)
| Node(Tree(value), Tree(value))
pub fn any(tree: Tree(a), check: fn(a) -> Bool) -> Bool {
case tree {
| Leaf(i) -> check(i)
| Node(left, right) -> any(left, check) || any(right, check)
}
}
pub fn has_even_leaf(tree: Tree(Int)) -> Bool {
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.
- Vim - https://github.com/gleam-lang/gleam.vim
- Emacs - https://github.com/MainShayne233/gleam-mode
- Visual Studio Code - https://github.com/rawburt/vscode-gleam-syntax
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.
Generating a project
Now a project can be generated like so:
gleam new 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
The project is managed and built using rebar3, the standard Erlang build tool. 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 to 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 we 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.
1 + 1 // => 2
5 - 1 // => 4
5 / 2 // => 2
3 * 3 // => 9
5 % 2 // => 1
2 > 1 // => True
2 < 1 // => False
2 >= 1 // => True
2 <= 1 // => False
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
2.0 >. 1.0 // => True
2.0 <. 1.0 // => False
2.0 >=. 1.0 // => True
2.0 <=. 1.0 // => False
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
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
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"
}
Pattern matching also works in let
bindings, though patterns that do not
match all instances of that type may result in a runtime error.
let [a] = [1] // a is 1
let [b] = [1, 2] // Runtime error! The pattern has 1 element but the value has 2
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)
}
Type annotations
Function arguments can be optionally be annotated with their type. The compiler will check these annotations and ensure they are correct.
fn identity(x: Int) -> Int {
x
}
Without an annotation this identity function would have have the inferred type
fn(a) -> a
, but the type annotation on the argument results in the type
of the function being fn(Int) -> Int
. This shows how type annotations can be
used to create functions with types less general than the compiler may have
inferred otherwise.
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.
Struct
Gleam's struct types are named collections of keys and values. They are similar to objects in object oriented languages, though they don't have methods.
Structs are defined with the struct
keyword.
pub struct Cat {
name: String
cuteness: Int
}
Here we have defined a struct called Cat
which has two fields: A name
field which is an Int
, and a cuteness
field which is an Int
.
The pub
keyword has been used to make this struct usable from other modules.
Once defined the struct type can be used in functions:
fn cats() {
// Struct fields can be given in any order
let cat1 = Cat(name: "Nubi", cuteness: 2001)
let cat2 = Cat(cuteness: 1805, name: "Biffy")
// Alternatively fields can be given without labels
let cat3 = Cat("Ginny", 1950)
[cat1, cat2, cat3]
}
Destructuring structs
To extract values from structs we can pattern match on them with let
or
case
.
let Cat(name: name1, cuteness: cuteness1) = cat1
name1 // => "Nubi"
cuteness1 // => 2001
We can also pattern match using positional arguments:
let Cat(name2, cuteness2) = cat2
name2 // => "Biffy"
cuteness2 // => 1805
Generic structs
Structs types can be parameterised so the same struct can be constructed with different contained types.
pub struct Box(a) {
tag: String,
contents: a, // The type of this field is injected when constructed
}
fn run() {
Box(tag: "one", contents: 1.0) // the type is Box(Float)
Box(tag: "two", contents: "2") // the type is Box(String)
}
Commonly used structs
Pair(a, b)
There may be times when you want to move two values together, or return them
both from a function. To save you from having to define a new struct for this
the Gleam standard library implements a Pair
type in the gleam/pair
module which can contain any two values.
import gleam/pair
fn run() {
pair.Pair("ok", 100) // type is Pair(String, Int)
pair.Pair(1.01, [1]) // type is Pair(Float, List(Int))
}
Erlang interop
At runtime Gleam structs are Erlang tuples. All information about the names of the keys is erased at runtime.
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
Commonly used enums
Bool
pub enum Bool =
| True
| False
As seen above Gleam's Bool
type is an enum! Use it to answer yes/no
questions and to indicate whether something is True
or False
.
Result(value, error)
pub enum Result(value, reason) =
| Ok(value)
| Error(reason)
Gleam doesn't have exceptions of null
to represent errors in our programs,
instead we have the Result
type. If a function call fail wrap the returned
value in a Result
, either Ok
if the function was successful, or Error
if it failed.
pub fn lookup(name, phone_book) {
// ... we found a phone number in the phone book for the given name here
Ok(phone_number)
}
The Error
type needs to be given a reason for the failure in order to
return, like so:
pub enum MyDatabaseError =
| InvalidQuery
| NetworkTimeout
pub fn insert(db_row) {
// ... something went wrong connecting to a database here
Error(NetworkTimeout)
}
In cases where we don't care about the specific error enough to want to create
a custom error type, or when the cause of the error is obvious without further
detail, the Nil
type can be used as the Error
reason.
pub fn lookup(name, phone_book) {
// ... That name wasn't found in the phone book
Error(Nil)
}
When we have a Result
type returned to us from a function we can pattern
match on it using case
to determine whether we have an Ok
result or
an Error
result.
The standard library gleam/result
module contains helpful functions for
working with the Result
type, make good use of them!
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?
- Will Gleam have type classes?
- How is message passing typed?
- How does Gleam compare to Alpaca?
- Should I put Gleam in production?
- Is it good?
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, I think so. :)