Language Tour

In this book we explore the fundamentals of the Gleam language, namely its syntax, core data structures, flow control features, and static type system. If you have some prior programming experience this will hopefully be enough to get you started with Gleam.

In some sections 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 
}

Strings

In Gleam Strings can be written as text surrounded by double quotes.

"Hello, Gleam!"

They can span multiple lines.

"Hello
Gleam!"

Under the hood Strings are UTF-8 encoded binaries and can contain any valid unicode.

"👩‍💻 こんにちは Gleam 💫"

Escape Sequences

Gleam supports common string escape sequences. Here's all of them:

SequenceResult
\nNewline
\rCarriage Return
\tTab
\"Double Quote
\\Backslash

For example to include a double quote (") character in a string literal it must be escaped by placing a backslash (\) character before it.

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

Similarly all backslash characters must be escaped:

// A Windows filepath C:\Users\Gleam
"C:\\Users\\Gleam"

// A Decorative border /\/\/\/\
"/\\/\\/\\/\\"

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.

Gleam does not define more bool operators like ! (negation). Instead, those are available as functions in the gleam/bool module.

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.

Binary, octal, and hexadecimal ints begin with 0b, 0o, and 0x respectively.

1
2
-3
4001
0b00001111
0o17
0xF

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

Underscores can be added to Ints for clarity.

1_000_000 // One million

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

Underscores can also be added to Floats for clarity.

1_000_000.0 // One million

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

Expression blocks

Every block in gleam is an expression. All expressions in the block are executed, and the result of the last expression is returned.

let value: Bool = {
    "Hello"
    42 + 12
    False
} // => False

Expression blocks can be used instead of parenthesis to change the precedence of operations.

let celsius = { fahrenheit - 32 } * 5 / 9

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 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]

Tuple

Lists are good for when we want a collection of one type, but sometimes we want to combine multiple values of different types. In this case tuples are a quick and convenient option.

Gleam provides two ways to construct or match on tuples: the #(1, 2, 3) format, introduced in Gleam 0.15.0, and the original tuple(1, 2, 3) format which will be removed in a future version of Gleam.

#(10, "hello") // Type is #(Int, String)
#(1, 4.2, [0]) // Type is #(Int, Float, List(Int))

Once you have a tuple the values contained can be accessed using the .0 accessor syntax.

let my_tuple = #("one", "two")
let first = my_tuple.0   // "one"
let seccond = my_tuple.1 // "two"

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 elements"
  _other -> "This list has more than 2 elements"
}

It's not just the top level data structure that can be pattern matched, 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

Matching on multiple values

Sometimes it is useful to pattern match on multiple values at the same time, so case supports having multiple subjects.

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

Assigning names to sub-patterns

Sometimes when pattern matching we want to assign a name to a value while specifying its shape at the same time. We can do this using the as keyword.

case xs {
  [[_, ..] as inner_list] -> inner_list
  other -> []
}

Checking equality and ordering in patterns

The if keyword can be used to add a guard expression to a case clause. Both the patterns have to match and the guard has to evaluate to True for the clause to match. The guard expression can check for equality or ordering for Int and Float.

case xs {
  [a, b, c] if a == b && b != c -> "ok"
  _other -> "ko"
}
case xs {
  [a, b, c] if a >. b && a <=. c -> "ok"
  _other -> "ko"
}

Alternative clause patterns

Alternative patterns can be given for a case clause using the | operator. If any of the patterns match then the clause matches.

Here the first clause will match if the variable number holds 2, 4, 6 or 8.

case number {
  2 | 4 | 6 | 8 -> "This is an even number"
  1 | 3 | 5 | 7 -> "This is an odd number"
  _ -> "I'm not sure"
}

If the patterns declare variables then the same variables must be declared in all patterns, and the variables must have the same type in all the patterns.

case list {
  [1, x] | x -> x // Error! Int != List(Int)
  _ -> 0
}

Functions

Named functions

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

pub fn add(x: Int, y: Int) -> Int {
  x + y
}

pub fn multiply(x: Int, y: Int) -> Int {
  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: fn(t) -> t, x: t) -> t {
  f(f(x))
}

pub fn add_one(x: Int) -> Int {
  x + 1
}

pub fn add_two(x: Int) -> Int {
  twice(add_one, x)
}

Pipe Operator

Gleam provides syntax for passing the result of one function to the arguments of another function, the pipe operator (|>). This is similar in functionality to the same operator in Elixir or F#.

The pipe operator allows you to chain function calls without using a plethora of parenthesis. For a simple example, consider the following implementation of string.reverse in Gleam:

iodata.to_string(iodata.reverse(iodata.new(string)))

This can be expressed more naturally using the pipe operator, eliminating the need to track parenthesis closure.

string
|> iodata.new
|> iodata.reverse
|> iodata.to_string

Each line of this expression applies the function to the result of the previous line. This works easily because each of these functions take only one argument. Syntax is available to substitute specific arguments of functions that take more than one argument; for more, look below in the section "Function capturing".

Type annotations

Function arguments are normally annotated with their type, and the compiler will check these annotations and ensure they are correct.

fn identity(x: some_type) -> some_type {
  x
}

fn inferred_identity(x) {
  x
}

The Gleam compiler can infer all the types of Gleam code without annotations and both annotated and unannotated code is equally safe. It's considered a best practice to always write type annotations for your functions as they provide useful documentation, and they encourage thinking about types as code is being written.

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: String, pattern: String, replacement: String) {
  // ...
}

It can be given labels like so.

pub fn replace(
  in string: String,
  each pattern: String,
  with replacement: String,
) {
  // 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.

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: Int , y: Int ) -> Int {
  x + y
}

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

In fact, this usage is so common that there is a special shorthand for it.

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

The pipe operator will first check to see if the left hand value could be used as the first argument to the call, e.g. a |> b(1, 2) would become b(a, 1, 2).

If not it falls back to calling the result of the right hand side as a function , e.g. b(1, 2)(a).

Modules

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 to 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.

Unqualified import

Values and types can also be imported in an unqualified fashion.

import animal/cat.{Cat, stroke}

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

This may be useful for values that are used frequently in a module, but generally qualified imports are preferred as it makes it clearer where the value is defined.

The prelude module

There is one module that is built into the language, the gleam prelude module. By default its types and values are automatically imported into every module you write, but you can still chose to import it the regular way. This may be useful if you have created a type or value with the same name as an item from the prelude.

import gleam

// This definition locally overrides the `Result` type 
// and the `Ok` constructor.
pub type Result {
  Ok
}

// The original `Result` and `Ok` can still be used
pub fn go() -> gleam.Result(Int) {
  gleam.Ok(1)
}

The prelude module contains these types:

  • BitString
  • Bool
  • Float
  • Int
  • List(element)
  • Nil
  • Result(value, error)
  • String
  • UtfCodepoint

And these values:

  • Error
  • False
  • Nil
  • Ok
  • True

Custom types

Gleam's custom types are named collections of keys and values. They are similar to objects in object oriented languages, though they don't have methods.

Custom types are defined with the type keyword.

pub type Cat {
  Cat(name: String, cuteness: Int)
}

Here we have defined a custom type called Cat. Its constructor is called Cat and it has two fields: A name field which is a String, and a cuteness field which is an Int.

The pub keyword makes this type usable from other modules.

Once defined the custom type can be used in functions:

fn cats() {
  // Labelled 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]
}

Multiple constructors

Custom types in Gleam can be defined with multiple constructors, making them a way of modeling data that can be one of a few different variants.

We've already seen a custom type with multiple constructors in the Language Tour - Bool.

Gleam's built-in Bool type is defined like this:

// A Bool is a value that is either `True` or `False`
pub type Bool {
  True
  False
}

It's a simple custom type with constructors that takes no arguments at all! Use it to answer yes/no questions and to indicate whether something is True or False.

The records created by different constructors for a custom type can contain different values. For example a User custom type could have a LoggedIn constructor that creates records with a name, and a Guest constructor which creates records without any contained values.

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

Destructuring

When given a custom type record we can pattern match on it to determine which record constructor matches, and to assign names to any contained values.

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

Custom types can also be destructured with a let binding.

type Score {
  Points(Int)
}
let score = Points(50)
let Points(p) = score

p // => 50

Named accessors

If a custom type has only one variant and named fields they can be accessed using .field_name.

For example using the Cat type defined earlier.

let cat = Cat(name: "Nubi", cuteness: 2001)
cat.name // This returns "Nubi"
cat.cuteness // This returns 2001

Generics

Custom types can be be parameterised with other types, making their contents variable.

For example, this Box type is a simple record that holds a single value.

pub type Box(inner_type) {
  Box(inner: inner_type)
}

The type of the field inner is inner_type, which is a parameter of the Box type. If it holds an int the box's type is Box(Int), if it holds a string the box's type is Box(String).

pub fn main() {
  let a = Box(123) // type is Box(Int)
  let b = Box("G") // type is Box(String)
}

Opaque types

At times it may be useful to create a type and make the constructors and fields private so that users of this type can only use the type through publically exported functions.

For example we can create a Counter type which holds an int which can be incremented. We don't want the user to alter the int value other than by incrementing it, so we can make the type opaque to prevent them from being able to do this.

// The type is defined with the opaque keyword
pub opaque type Counter {
  Counter(value: Int)
}

pub fn new() {
  Counter(0)
}

pub fn increment(counter: Counter) {
  Counter(counter.value + 1)
}

Because the Counter type has been marked as opaque it is not possible for code in other modules to construct or pattern match on counter values or access the value field. Instead other modules have to manipulate the opaque type using the exported functions from the module, in this case new and increment.

Record updates

Gleam provides a dedicated syntax for updating some of the fields of a custom type record.

pub type Person {
  Person(
    name: String,
    gender: Option(String),
    shoe_size: Int,
    age: Int,
    is_happy: Bool,
  )
}

pub fn have_birthday(person) {
  // It's this person's birthday, so increment their age and
  // make them happy
  Person(..person, age: person.age + 1, is_happy: true)
}

As Gleam records are immutable the update syntax does not alter the fields in place, instead it created a new record with the values of the initial record with the new values added.

Erlang interop

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

Custom type records with contained values are Erlang records. The Gleam compiler generates an Erlang header file with a record definition for each constructor, for use from Erlang.

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

Result(value, error)

pub type Result(value, reason) {
  Ok(value)
  Error(reason)
}

Gleam doesn't have exceptions or null to represent errors in our programs, instead we have the Result type. If a function call fails, 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 type 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!

Try

In Gleam if a function can either succeed or fail then it normally will return the Result type. With Result, a successful return value is wrapped in an Ok record, and an error value is wrapped in an Error record.

// parse_int(String) -> Result(Int, String)

parse_int("123") // -> Ok(123)
parse_int("erl") // -> Error("expected a number, got `erl`")

When a function returns a Result we can pattern match on it to handle success and failure:

case parse_int("123") {
  Error(e) -> io.println("That wasn't an Int")
  Ok(i) -> io.println("We parsed the Int")
}

This is such a common pattern in Gleam that the try syntax exists to make it more concise.

try int_a = parse_int(a)
try int_b = parse_int(b)
try int_c = parse_int(c)
Ok(int_a + int_b + int_c)

When a variable is declared using try Gleam checks to see whether the value is an Error or an Ok record. If it's an Ok then the inner value is assigned to the variable:

try x = Ok(1)
Ok(x + 1)
// -> Ok(2)

If it's an Error then the Error is returned immediately:

try x = Error("failure")
Ok(x + 1)
// -> Error("failure")

Assert

Some times we have a function that can technically fail, but in practice we don't expect it to happen. For example our program may start by opening a file, if we know that the file is always going to be possible to open, then we don't want to complicate our program with handling an error that should never happen.

Other times we have errors that may occur, but we don't have any way of realistically handling them within our program. For example if we have a web application that talks to a database when handling each HTTP request and that database stops responding, then we have a fatal error that cannot be recovered from. We could detect the error in our code, but what do we do then? We need the database to handle the request.

Lastly we may think errors are possible, but we are writing a quick script or prototype application, so we want to only spend time on the success path for now.

For these situations Gleam provides assert, a keyword that causes the program to crash if a pattern does not match.

assert Ok(i) = parse_int("123")
i // => 123

Here the assert keyword has been used to say "this function must return an Ok value" and we haven't had to write any error handling. The inner value is assigned the variable i and the program continues.

assert Ok(i) = parse_int("not an int")

In this case the parse_int function returns an error, so the Ok(i) pattern doesn't match and so the program crashes.

Surviving crashes

Being fault tolerant and surviving crashes is a key part of Erlang's error handling strategy, and as an Erlang based language Gleam can also take advantage of this. To find out more about Erlang fault tolerance see the Gleam OTP project and the Learn You Some Erlang chapter on supervisors.

Todo

Gleam's todo keyword is used to indicate that some code is not yet finished.

It can be useful when designing a module, type checking functions and types but leaving the implementation of the functions until later.

fn favourite_number() -> Int {
  // The type annotations says this returns an Int, but we don't need
  // to implement it yet.
  todo
}

pub fn main() {
  favourite_number() * 2
}

When this code is built Gleam will type check and compile the code to ensure it is valid, and the todo will be replaced with code that crashes the program if that function is run.

A message can be given as a form of documentation. The message will be printed in the error message when the todo code is run.

fn favourite_number() -> Int {
  todo("We're going to decide which number is best tomorrow")
}

When the compiler finds a todo it will print a warning, which can be useful to avoid accidentally forgetting to remove a todo.

The warning also includes the expected type of the expression that needs to replace the todo. This can be a useful way of asking the compiler what type is needed if you are ever unsure.

fn main() {
  my_complicated_function(
    // What type does this function take again...?
    todo
  )
}

Constants

Gleam's module constants provide a way to use a certain fixed value in multiple places in a Gleam project.

pub const start_year = 2101
pub const end_year = 2111

pub fn is_before(year: Int) -> Bool {
  year < start_year
}

pub fn is_during(year: Int) -> Bool {
  start_year <= year && year <= end_year
}

Like all values in Gleam constants are immutable and their values cannot be changed, so they cannot be used as global mutable state.

When a constant is referenced the value is inlined by the compiler, so they can be used in case expression guards.

pub const start_year = 2101
pub const end_year = 2111

pub describe(year: Int) -> String {
  case year {
    year if year < start_year -> "Before"
    year if year > end_year -> "After"
    _ -> "During"
  }
}

Type annotations

Constants can also be given type annotations.

pub const name: String = "Gleam"
pub const size: Int = 100

These annotations serve as documentation or can be used to provide a more specific type than the compiler would otherwise infer.

Type aliases

Type aliases are a way of creating a new name for an existing type. This is useful when the name of the type may be long and awkward to type repeatedly.

Here we are giving the type List(#(String, String)) the new name Headers. This may be useful in a web application where we want to write multiple functions that return headers.

pub type Headers =
  List(#(String, String))

Bit strings

Gleam has a convenient syntax for working directly with binary data called a Bit String. Bit Strings represent a sequence of 1s and 0s.

Bit Strings are written literally with opening brackets <<, any number of bit string segments separated by commas, and closing brackets >>.

Bit String Segments

By default a Bit String segment represents 8 bits, also known as 1 byte.

// This is the number 3 as an 8 bit value.
// Written in binary it would be 00000011
<<3>>

You can also specify a bit size using either short hand or long form.

// These are the exact same value as above
// Shorthand
<<3:8>>

// Long Form
<<3:size(8)>>

You can specify any positive integer as the bit size.

// This is not same as above, remember we're working with a series of 1s and 0s.
// This Bit String is 16 bits long: 0000000000000011
<<3:size(16)>>

You can have any number of segments separated by commas.

// This is True
<<0:4, 1:3, 1:1>> == <<3>>

Bit String Segment Options

There are a few more options you can attach to a segment to describe its size and bit layout.

unit() lets you create a segment of repeating size. The segment will represent unit * size number of bits. If you use unit() you must also have a size option.

// This is True
<<3:size(4)-unit(4)>> == <<3:size(16)>>

The utf8, utf16 and utf32 options let you put a String directly into a Bit String.

<<"Hello Gleam 💫":utf8>>

The bit_string option lets you put any other Bit String into a Bit String.

let a = <<0:1, 1:1, 1:1>>
<<a:bit_string, 1:5>> == <<"a":utf8>> // True

Here Is the full list of options and their meaning:

Options in Values

OptionMeaning
bit_stringa bitstring that is any bit size
floatdefault size of 64 bits
intdefault size of 8 bits
sizethe size of the segment in bits
unithow many times to repeat the segment, must have a size
bigbig endian
littlelittle endian
nativeendianness of the processor
utf8a string to encode as utf8 codepoints
utf16a string to encode as utf16 codepoints
utf32a string to encode as utf32 codepoints

Options in Patterns

OptionMeaning
binarya bitstring that is a multiple of 8 bits
bit_stringa bitstring that is any bit size
floatfloat value, size of exactly 64 bits
intint value, default size of 8 bits
bigbig endian
littlelittle endian
nativeendianness of the processor
signedthe captured value is signed
unsignedthe captured value is unsigned
sizethe size of the segment in bits
unithow many times to repeat the segment, must have a size
utf8an exact string to match as utf8 codepoints
utf16an exact string to match as utf16 codepoints
utf32an exact string to match as utf32 codepoints
utf8_codepointa single valid utf8 codepoint
utf16_codepointa single valid utf16 codepoint
utf32_codepointa single valid utf32 codepoint

Values vs Patterns

Bit Strings can appear on either the left or the right side of an equals sign. On the left they are called patterns, and on the right they are called values.

This is an important distinction because values and patterns have slightly different rules.

Rules for Patterns

You can match on a variable length segment with the bit_string or binary options. A pattern can have at most 1 variable length segment and it must be the last segment.

In a pattern the types utf8, utf16, and utf32 must be an exact string. They cannot be a variable. There is no way to match a variable length section of a binary with an exact encoding.

You can match a single variable codepoint with utf8_codepoint, utf16_codepoint, and utf32_codepoint which will match the correct number of bytes depending on the codepoint size and data.

Further Reading

Gleam inherits its Bit String syntax and handling from Erlang. You can find the Erlang documentation here.

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"

Labelled arguments

Like regular functions, external functions can have labelled arguments.

pub external fn any(in: List(a), satisfying: fn(a) -> Bool) =
  "my_external_module" "any"

This function has the labelled arguments in and satisfying, and can be called like so:

any(in: my_list, satisfying: is_even)
any(satisfying: is_even, in: my_list)

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"