Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

What is Gleam?

Gleam is a modern, friendly programming language for building type-safe and scalable systems. It compiles to Erlang and JavaScript, and integrates seamlessly with other BEAM languages like Erlang, Elixir, and LFE.

A simple Gleam program looks like this:

import gleam/io

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

Gleam combines the guarantees of a strong type system, the expressiveness of functional programming, and the reliability of the highly concurrent Erlang runtime.

Reliability and scalability

Running on the Erlang virtual machine - the same platform behind large-scale systems such as WhatsApp - Gleam is built for production workloads of any size.

The BEAM runtime enables:

  • Multi-core, actor-based concurrency with millions of lightweight processes.
  • Immutable data structures for safe parallelism.
  • A concurrent garbage collector that never pauses the entire system.
fn spawn_task(i) {
  task.async(fn() {
    let n = int.to_string(i)
    io.println("Hello from " <> n)
  })
}

pub fn main() {
  list.range(0, 200_000)
  |> list.map(spawn_task)
  |> list.each(task.await_forever)
}

Use syntax

Gleam’s use syntax lets you write callback-based APIs in a flat, top-to-bottom style, killing the “pyramid of doom”.

It’s pure syntax sugar that rewrites the rest of the block into an anonymous function passed to the callee, so you keep the same semantics (no new runtime magic) but lose the nesting - making control flow and error cases much easier to read and maintain:

pub fn with_use() -> Result(String, Nil) {
  use username <- result.try(get_username())
  use password <- result.try(get_password())
  use greeting <- result.map(log_in(username, password))
  greeting <> ", " <> username
}

Without use, the same logic requires nested anonymous functions:

pub fn without_use() -> Result(String, Nil) {
  result.try(get_username(), fn(username) {
    result.try(get_password(), fn(password) {
      result.map(log_in(username, password), fn(greeting) {
        greeting <> ", " <> username
      })
    })
  })
}

Built-in tools

Gleam ships with everything you need: a compiler, build tool, formatter, package manager, and editor integrations. Starting a new project is as simple as:

gleam new my_project

The ecosystem includes thousands of packages from Gleam, Erlang, and Elixir. For example:

➜ gleam add gleam_json
      Added gleam_json v0.5.0
➜ gleam test
   Running app_test.main
.
1 tests, 0 failures

Developer-friendly

Gleam avoids common pitfalls like null values and exceptions. Its type system and error messages are designed to be practical and approachable:

error: Unknown record field

  ┌─ ./src/app.gleam:8:16
  │
8 │ user.alias
  │     ^^^^^^ Did you mean `name`?

The value being accessed has this type:
    User

It has these fields:
    .name

Interoperability

Gleam works smoothly with other BEAM languages, giving you access to a vast ecosystem of libraries. It can also compile to JavaScript, complete with TypeScript definitions for safe interoperability.

@external(erlang, "Elixir.HPAX", "new")
pub fn new(size: Int) -> Table

pub fn register_event_handler() {
  let el = document.query_selector("a")
  element.add_event_listener(el, fn() {
    io.println("Clicked!")
  })
}

Learn more

Quick start

Installing Erlang

Since Gleam primarily compiles to Erlang (with optional JavaScript support), you’ll need Erlang installed on your system first.

On Linux and macOS:

  • Homebrew (macOS): brew install erlang
  • MacPorts (macOS): port install erlang
  • Ubuntu/Debian: apt-get install erlang
  • Fedora: yum install erlang
  • Arch Linux/Manjaro: pacman -S erlang
  • FreeBSD: pkg install erlang

On Windows: Download and install Erlang using the official installers:

Installing Gleam

The simplest way to get Gleam is from the GitHub releases page. Download the latest release for your operating system and follow the installation instructions.

Building from Source

You may want to build Gleam from source if you’re contributing to the language or compiling it for WebAssembly.

  1. Clone the repository:

    git clone https://github.com/gleam-lang/gleam
    cd gleam
    
  2. Install Rust: Gleam’s compiler is written in Rust. Install it via rustup:

    • Linux/macOS:

      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
      
    • Windows: Download the installer from the Rust website, run it, then use:

      rustup toolchain install stable
      
  3. Build the compiler:

    cargo install --path gleam-bin 
    

    This installs Gleam globally on your system.

Building for WebAssembly

To compile Gleam for WebAssembly, install wasm-pack:

  • Linux/macOS:

    curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
    
  • Windows (alternative):

    cargo install wasm-pack
    

Then build the WebAssembly target:

cd compiler-wasm
wasm-pack build --target web

Hello world

Let us now write our first Gleam program! If you don’t have Gleam installed, it is recommended to follow instructions in the previous section. Run:

gleam new hello_world
cd hello_world

You can now see the automatically generated Gleam project structure:

hello_world
|_ .github/workflows
   |_ test.yml
|_ src
   |_ hello_world.gleam
|_ test
   |_ hello_world_test.gleam
|_ .gitignore
|_ gleam.toml
|_ README.md

The entry point is located in the src/hello_world.gleam file. It will probably look something like this:

import gleam/io

pub fn main() {
  io.println("Hello from hello_world!")
}

Let us change our program, so that it prints canonical Hello, world! message:

import gleam/io

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

As you can see, Gleam doesn’t require semicolons. To run the program, you can use gleam run command:

$ gleam run
  Compiling gleam_stdlib
  Compiling gleeunit
  Compiling hello_world
   Compiled in 1.78s
    Running hello_world.main
Hello, world!

By default, it transpiles our code to erlang and uses it to run our code. However, you can also use javascript using --target flag:

$ gleam run --target javascript
  Compiling gleam_stdlib
  Compiling gleeunit
  Compiling hello_world
   Compiled in 1.5s
    Running hello_world.main
Hello, world!

You can also use specific javascript runtime using --runtime flag:

$ gleam run --target javascript --runtime node
  Compiling gleam_stdlib
  Compiling gleeunit
  Compiling hello_world
   Compiled in 1.5s
    Running hello_world.main
Hello, world!

Basics

Who This Tutorial Is For

This guide is for people who already know how to code. If you’re familiar with variables, functions, types, and control flow, you’re in the right spot.

We won’t explain the basics of programming. Instead, we’ll focus on how things work in Gleam and on the BEAM. If you’re coming from Python, JavaScript, Go, C++, or similar, you’ll be fine - even if functional programming is new to you.

You don’t need to know FP going in. This is aimed at curious developers who want a solid intro to a functional language that runs on the BEAM. It follows the style of guides like Learn You a Haskell, which assume programming experience but not a background in FP.

Think of this as a fast-moving tour from one developer to another. Like the Go Tour or the Rust Book, it covers practical syntax and patterns instead of explaining what a loop is.

Comments

Comments

You can add comments to your Gleam code with //. They help explain intent and improve readability.

Here’s a simple example that adds a comment above a print call:

import gleam/io

pub fn main() {
  // Show a message
  io.println("Hello world!")
}

For longer explanations, use // at the start of each line:

// This is a longer comment.
// Each line starts with //
// so the compiler treats it as a comment.

Booleans and numbers

Booleans

Gleam uses True and False as boolean values. Their type is Bool. You can apply logical operators like:

False && False  // False
True && False   // False
True && True    // True

False || False  // False
True || False   // True
True || True    // True

Both operators short-circuit:

  • False && ... skips the right side.
  • True || ... skips the right side.
import gleam/io

fn message() -> Bool {
  io.println("evaluated")
  True
}

pub fn main() {
  let _ = False && message()  // doesn't print anything
  let _ = True && message()   // prints "evaluated"
}

Printing Booleans

Convert a Bool to a String before printing:

import gleam/io
import gleam/bool

pub fn main() {
  io.println(bool.to_string(False))
}

You can also use echo for debugging:

pub fn main() {
  echo True
}

Negation

Negate with ! or bool.negate:

import gleam/bool

!True              // False
bool.negate(False) // True

Numbers

Gleam supports two numeric types:

  • Int for whole numbers
  • Float for decimal numbers

Integers

Integers support basic arithmetic:

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

Division by zero returns 0:

5 / 0 // => 0

Use int.divide to handle division safely:

import gleam/int

int.divide(5, 2) // Ok(2)
int.divide(5, 0) // Error(Nil)

You can write large numbers with _:

1_000_000

Gleam also supports binary, octal, and hexadecimal:

0b1010
0o755
0xFF

Comparison is straightforward:

1 > 0   // True
2 <= 2  // True

Working with Integers

Convert to string:

import gleam/int
int.to_string(42)

Convert to float:

int.to_float(2)  // 2.0

Other helpers:

int.absolute_value(-10)   // 10
int.negate(5)             // -5
int.min(1, 3)             // 1
int.max(1, 3)             // 3
int.square_root(4)        // Ok(2.0)
int.square_root(-1)       // Error(Nil)
int.is_even(2)            // True
int.is_odd(3)             // True

Convert to base:

int.to_base2(5)    // "101"
int.to_base16(255) // "FF"
int.to_base_string(48, 36) // Ok("1C")

Invalid base values return an error:

int.to_base_string(48, 1)  // Error(InvalidBase)
int.to_base_string(48, 37) // Error(InvalidBase)

JavaScript Note

On JavaScript targets, keep integers within ±(2^53 − 1) to avoid precision issues:

int.to_string(9_999_999_999_999_999)
// => "10000000000000000" on JS
// => "9999999999999999" on BEAM

The compiler warns when you exceed this range on the JS target.

Float Numbers

Float literals:

1.5
-0.25
8.0

You can use underscores for clarity:

3.141_592

Scientific notation is also supported:

6.022e23

Float Operations

Use +., -., *., and /. for float math:

2.0 +. 3.0   // 5.0
5.0 -. 2.0   // 3.0
3.0 *. 2.0   // 6.0
4.0 /. 2.0   // 2.0

Dividing by zero yields 0.0. Use float.divide to handle it safely:

import gleam/float

float.divide(3.0, 0.0) // Error(Nil)

Comparisons use float-specific operators:

2.0 >. 1.0    // True
2.0 <=. 2.0   // True
2.0 == 2.0    // True
2.0 != 3.0    // True

Float Helpers

float.absolute_value(-2.5)  // 2.5
float.negate(1.0)           // -1.0
float.min(2.1, 3.3)         // 2.1
float.max(2.1, 3.3)         // 3.3
float.truncate(2.99)        // 2
float.ceiling(1.4)          // 2.0
float.floor(1.4)            // 1.0
float.round(2.5)            // 3.0

Float Power and Root

float.power(2.0, 3.0)        // Ok(8.0)
float.power(-1.0, 0.5)       // Error(Nil)
float.square_root(4.0)       // Ok(2.0)
float.square_root(-1.0)      // Error(Nil)

As with integers, you must pass Float to these functions. Gleam doesn’t auto-cast from Int.

Strings

Strings

In Gleam, strings are UTF-8 binaries. They’re immutable and store any Unicode text. Operations like length, slicing, and reversing work on graphemes (what users think of as characters), so they won’t split composed characters.

On the BEAM backend, Gleam uses Erlang’s Unicode-aware functions. On JavaScript, it uses UTF-16 under the hood, but the standard library ensures grapheme-level correctness.

Use double quotes with standard escapes (\", \\, \n, \t, \u{...}). Concatenate with <>:

"Hello, Gleam!\n" <> "\u{1F44B}"  // "Hello, Gleam! 👋"

Emptiness and Length

string.is_empty checks for an empty string without allocating. string.length returns the grapheme count. string.byte_size gives the UTF-8 byte length.

string.is_empty("")        // True
string.length("ß↑e̊")       // 3 graphemes
string.byte_size("ß↑e̊")    // 8 bytes

Reverse

string.reverse works on graphemes:

string.reverse("stressed")  // "desserts"

Replace

string.replace replaces all non-overlapping literal substrings:

string.replace(in: "www.example.com", each: ".", with: "-")
// "www-example-com"

Case Conversion

lowercase and uppercase are Unicode-aware but not locale-sensitive:

string.lowercase("X-FILES")  // "x-files"
string.uppercase("skinner")  // "SKINNER"

Comparison

Lexicographic comparison using graphemes:

string.compare("A", "B")  // order.Lt

Slicing

slice uses grapheme indices and returns "" for out-of-bounds:

string.slice(from: "gleam", at_index: 1, length: 2)  // "le"

Cropping and Dropping

Remove prefixes or graphemes:

string.crop(from: "The Lone Gunmen", before: "Lone") // "Lone Gunmen"
string.drop_start(from: "  hats", up_to: 2)          // "hats"

Contains, Prefix, Suffix

All are case-sensitive literal checks:

string.contains(does: "theory", contain: "ory") // True

Splitting

split returns all parts, including trailing empty strings. split_once stops after the first match:

string.split("a/b/c/", on: "/")          // ["a", "b", "c", ""]
string.split_once("a/b/c/", on: "/")     // Ok(#("a", "b/c/"))

Joining

append, concat, and join copy strings:

string.join(["home", "evan"], with: "/")  // "home/evan"

Repeat and Pad

Work on graphemes:

string.repeat("ha", times: 3)           // "hahaha"
string.pad_start("121", to: 5, with: ".") // "..121"

Trimming

Removes Unicode whitespace:

string.trim("  hats  \n")  // "hats"

Grapheme-Level Access

pop_grapheme and to_graphemes let you iterate safely:

string.pop_grapheme("gleam")  // Ok(#("g", "leam"))

Code Points

Work at the Unicode scalar level:

let cps = string.to_utf_codepoints("🏳️‍🌈")
string.from_utf_codepoints(cps)  // "🏳️‍🌈"

Other Helpers

string.first("icecream")        // Ok("i")
string.capitalise("mamouna")    // "Mamouna"
string.to_option("")            // None

Tuples and lists

Lists

A list is an ordered collection of values. Lists are generic, so their type tracks the kind of elements they hold, like List(Int) or List(String).

Internally, lists are immutable and singly-linked. This makes operations at the front fast and anything involving the end slower. For that reason, Gleam code usually builds lists from the front.

Use the gleam/list module for transformations like map, filter, and fold. This works the same on both BEAM and JavaScript targets.

List Literals

Use square brackets. An empty list is []. All elements must be the same type.

let empty_list = []
let letters = ["a", "b", "c"]

Prepend (Fast)

Adding to the front is efficient and doesn’t copy the list.

let tail = ["b", "c"]
let full = ["a", ..tail]  // ["a", "b", "c"]

The .. spread works only at the start of a list. It’s not a general-purpose concat operator.

Pattern Matching

You can match on lists using case, useful for handling different shapes.

case letters {
  [] -> "Empty"
  [x] -> "One: " <> x
  [first, ..rest] -> "First is " <> first
}

This helps cleanly separate the empty case, a single item, or a longer list.

Length, Emptiness, Membership

import gleam/list

list.length([1, 2, 3])           // 3
list.is_empty([])                // True
list.contains(["a", "b"], "b")   // True

These scan the list, so avoid using them in tight loops.

Head and Tail

list.first(["x", "y"])   // Ok("x")
list.rest(["x", "y"])    // Ok(["y"])

Both are fast and do not copy data.

Map, Filter, Fold

list.map([1, 2], fn(x) { x + 1 })              // [2, 3]
list.filter([1, 2, 3], fn(x) { x > 1 })        // [2, 3]
list.fold([1, 2, 3], 0, fn(acc, x) { acc + x }) // 6

These replace manual recursion for most list processing.

Find, Any, All

list.find([1, 2, 3], fn(x) { x == 2 })   // Ok(2)
list.any([3, 5], fn(x) { x < 4 })        // True
list.all([3, 5], fn(x) { x > 2 })        // True

any and all stop as soon as possible.

Zip and Unzip

list.zip([1, 2], ["a", "b"])               // [#(1, "a"), #(2, "b")]
list.unzip([#(1, "x"), #(2, "y")])         // #([1, 2], ["x", "y"])

Use strict_zip if you want to fail on length mismatch.

Append, Flatten, Flat Map

list.append(["a"], ["b"])                  // ["a", "b"]
list.flatten([["a"], ["b", "c"]])          // ["a", "b", "c"]
list.flat_map(["x", "y"], fn(s) { [s, "!"] }) // ["x", "!", "y", "!"]

Appending copies the first list, so avoid chaining it repeatedly. Use a fold and reverse for better performance.

Take and Drop

list.take(["a", "b", "c"], 2)  // ["a", "b"]
list.drop(["a", "b", "c"], 1)  // ["b", "c"]

Both are safe and return smaller lists without error.

Unique and Sort

list.unique(["x", "x", "y"])             // ["x", "y"]
list.sort([3, 1, 2], by: int.compare)    // [1, 2, 3]

unique keeps the first of each value. sort needs a comparison function.

Windows, Chunks, Partition

list.window([1, 2, 3, 4], by: 2)        // [[1,2],[2,3],[3,4]]
list.sized_chunk([1, 2, 3, 4], into: 2) // [[1,2],[3,4]]
list.partition([1, 2, 3], int.is_even)  // #([2], [1, 3])

Use these to split or batch data.

Ranges and Repeats

list.range(1, 3)         // [1, 2, 3]
list.repeat("hi", 2)     // ["hi", "hi"]

Useful for generating test data or index sequences.

When Not to Use Lists

If you need random access, frequent edits in the middle, or lookups by key, lists aren’t the right tool. Try gleam/dict for key-value data or rewrite the algorithm to use folds.

list.at lets you read by index, but it’s O(n) and returns a result:

list.at(["a", "b"], 1) // Ok("b")

Tuples

Tuples hold a fixed number of values. Each element can be a different type. They’re good for short groups like a result pair or coordinates.

Tuple Literals

let status = #(404, "Not Found")
let config = #("dev", True, 3)

Each arity has a different type. A pair is not the same as a triple.

Access by Position

status.0   // 404
status.1   // "Not Found"
config.2   // 3

Accessors use zero-based indexing and don’t require pattern matching.

Pattern Matching

case config {
  #(env, active, tries) -> env <> ": " <> int.to_string(tries)
}

Use matching when you want to name all fields. If the tuple has meaning in your domain, consider using a record or custom type instead.

Assignments

Assignments

Use let to bind values. Names use snake_case. Bindings are immutable, but you can bind the same name again later and shadow the previous value. This does not change the original value, it only replaces which value the name refers to.

import gleam/io

pub fn main() {
  let title = "Morning"
  io.println(title) // Morning

  let copy = title
  io.println(copy)  // Morning

  let title = "Evening"
  io.println(title) // Evening

  io.println(copy)  // Morning
}

If a binding is never used, the compiler warns you. Prefix the name with an underscore when this is intentional.

pub fn main() {
  let _unused_score = 500
}

You can add type annotations to bindings. These help with clarity and catch mismatches during type checking.

pub fn main() {
  let _label: String = "Gleam"
  let _active: Bool = True
  let _count: Int = 3
}

Pattern matching

Pattern Matching

Gleam supports pattern matching in variable bindings and case expressions. This allows you to unpack data and handle different shapes clearly and safely.

Let Bindings

With let, your pattern must always match the value. It’s for shapes you know are guaranteed.

pub fn main() {
  let point = #(3, 4)
  let #(x, y) = point

  io.println(int.to_string(x + y))
}

This works because point is a 2-tuple, and the pattern matches that exactly.

Case Expressions

Use case to match multiple possible shapes. For strings, you can match a prefix using concatenation:

import gleam/io

pub fn main() {
  io.println(get_name("Hello, Joe"))
  io.println(get_name("Hello, Mike"))
  io.println(get_name("System still working?"))
}

fn get_name(x: String) -> String {
  case x {
    "Hello, " <> name -> name
    _ -> "Unknown"
  }
}

Lists can be matched by length or contents. The .. syntax captures the remaining elements:

import gleam/int
import gleam/list
import gleam/io

pub fn main() {
  let xs = list.repeat(int.random(5), times: int.random(3))

  let result = case xs {
    [] -> "Empty list"
    [1] -> "List of just 1"
    [4, ..] -> "List starting with 4"
    [_, _] -> "List of 2 elements"
    _ -> "Some other list"
  }

  io.println(result)
}

Let Assert

Use let assert when you expect a certain shape and want the program to fail fast if that shape isn’t matched. It allows partial patterns and will panic if the match fails. You can add a custom message.

pub fn main() {
  let a = unsafely_get_first_element([123])
  io.println(int.to_string(a))

  let b = unsafely_get_first_element([])
  io.println(int.to_string(b))  // Panics here
}

pub fn unsafely_get_first_element(items: List(a)) -> a {
  let assert [first, ..] = items as "List should not be empty"
  first
}

Use this only when you’re certain about the input, or when failing is acceptable.

Functions

Functions

Use fn to define a function. Gleam runs the body top to bottom and returns the final value. There’s no return keyword.

Functions are private unless marked pub. Type annotations help with clarity but aren’t required.

pub fn main() {
  greet("Alex")
}

fn greet(name: String) {
  let message = make_message(name)
  io.println(message)
}

fn make_message(name: String) -> String {
  "Welcome, " <> name <> "!"
}

Higher-Order Functions

Functions can be passed around like any other value. You can use them to build flexible logic.

pub fn main() {
  let result = apply_twice(square, 3)
  io.println(int.to_string(result))
}

fn square(n: Int) -> Int {
  n * n
}

fn apply_twice(f: fn(Int) -> Int, x: Int) -> Int {
  f(f(x))
}

Anonymous Functions

You can define unnamed functions inline. These can also capture values from their environment.

pub fn main() {
  let double = fn(n) { n * 2 }
  let result = double_and_add(double, 5)
  io.println(int.to_string(result))

  let base = 10
  let add_base = fn(x) { x + base }
  io.println(int.to_string(add_base(5)))
}

fn double_and_add(f: fn(Int) -> Int, x: Int) -> Int {
  f(x) + x
}

Function Captures

Use _ to partially apply a function and create a new one.

pub fn main() {
  let greet = say("Hi", _)
  io.println(greet("Sam"))
}

fn say(prefix: String, name: String) -> String {
  prefix <> ", " <> name
}

This simplifies your code and makes pipelines easier to read.

Generic Functions

Generic functions work with any type, as long as it’s consistent within the call.

pub fn main() {
  io.println(duplicate("hey"))
  io.println(int.to_string(duplicate(42)))
}

fn duplicate(x: value) -> Tuple(value, value) {
  #(x, x)
}

The compiler ensures the types are used consistently and doesn’t allow mismatches.

Pipelines

The pipe operator passes the result of one expression into the next. It improves readability by following the data flow.

import gleam/string
import gleam/io

pub fn main() {
  "   Hello World!  "
  |> string.trim
  |> string.uppercase
  |> string.append("!!!")
  |> io.println
}

You can also use _ to place the piped value into another argument slot.

"42"
|> string.concat(["Value: ", _])
|> io.println

Labelled Arguments

Labels help show what each argument means. They’re useful when multiple arguments share a type.

pub fn main() {
  let total = compute_cost(units: 4, price_per_unit: 25)
  io.println(int.to_string(total))
}

fn compute_cost(units units: Int, price_per_unit cost: Int) -> Int {
  units * cost
}

Labels can be written in any order at the call site.

Label Shorthand

If your variable names match the parameter labels, you can use shorthand syntax.

pub fn main() {
  let width = 10
  let height = 5
  let unit = "cm"

  describe_rectangle(width:, height:, unit:)
}

fn describe_rectangle(
  width width: Int,
  height height: Int,
  unit unit: String,
) {
  let info = int.to_string(width) <> " x " <>
             int.to_string(height) <> " " <>
             unit

  io.println(info)
}

This keeps function calls compact and clear.

Result and optional types

Handling absence and failure

Gleam uses two types to handle missing values and fallible operations: Option(a) and Result(a, e). These types make uncertainty explicit and force you to handle it clearly.

Option Type

Use Option(a) for values that might be missing. For example, an optional parameter or a field that may be absent:

import gleam/option.{type Option, Some, None}

pub type User {
  User(name: String, nickname: Option(String))
}

let user1 = User("Eva", Some("E"))
let user2 = User("Tom", None)

option.map

Transforms the inner value if it’s present:

option.map(Some(2), fn(x) { x * 3 }) // Some(6)
option.map(None, fn(x) { x * 3 })    // None

option.then

Chains computations that return Option:

option.then(Some(2), fn(x) { Some(x + 1) }) // Some(3)
option.then(Some(2), fn(_) { None })        // None
option.then(None, fn(x) { Some(x + 1) })    // None

option.or and option.lazy_or

Use fallback options:

option.or(Some(1), Some(2))               // Some(1)
option.or(None, Some(2))                  // Some(2)
option.lazy_or(Some(1), fn() { Some(3) }) // Some(1)
option.lazy_or(None, fn() { Some(3) })    // Some(3)

option.unwrap

Extract the value or use a default:

option.unwrap(Some(5), 0) // 5
option.unwrap(None, 0)    // 0

option.flatten

Removes one layer of nesting:

option.flatten(Some(Some(1))) // Some(1)
option.flatten(Some(None))    // None
option.flatten(None)          // None

option.to_result

Converts an Option into a Result:

option.to_result(Some(5), "missing") // Ok(5)
option.to_result(None, "missing")    // Error("missing")

Result Types

Use Result(a, e) when a function can fail. This forces the caller to handle both the success and error cases.

fn parse_number(s: String) -> Result(Int, String) {
  case int.parse(s) {
    Ok(n) -> Ok(n)
    Error(_) -> Error("invalid input")
  }
}

result.map

Transforms the success value:

result.map(Ok(2), fn(x) { x * 2 })     // Ok(4)
result.map(Error("bad"), fn(x) { x })  // Error("bad")

result.try

Chains fallible functions:

fn safe_parse(s: String) -> Result(Int, String) {
  case int.parse(s) {
    Ok(n) -> Ok(n)
    Error(_) -> Error("not a number")
  }
}

result.try(Ok("42"), safe_parse)   // Ok(42)
result.try(Ok("foo"), safe_parse)  // Error("not a number")
result.try(Error("fail"), safe_parse) // Error("fail")

result.unwrap

Returns the success value or a fallback:

result.unwrap(Ok("done"), "default")    // "done"
result.unwrap(Error("fail"), "default") // "default"

result.is_ok and result.is_error

Check what variant you’re working with:

result.is_ok(Ok(1))       // True
result.is_error(Ok(1))    // False
result.is_error(Error(1)) // True

result.all

Collects a list of Results into one Result with a list:

result.all([Ok(1), Ok(2)])        // Ok([1, 2])
result.all([Ok(1), Error("x")])   // Error("x")

result.lazy_or

Provides a fallback result lazily:

result.lazy_or(Ok(1), fn() { Ok(2) })          // Ok(1)
result.lazy_or(Error("e"), fn() { Ok(2) })     // Ok(2)
result.lazy_or(Error("e"), fn() { Error("x") }) // Error("x")

When to Use Each

In other languages fallible functions may return either Result or Option depending on whether there is more information to be given about the failure. In Gleam all fallible functions return Result, and Nil is used as the error if there is no extra detail to give.

This consistency removes the boilerplate that would otherwise be needed to convert between Option and Result types, and makes APIs more predictable:

import gleam/option
import gleam/result
import gleam/int
import gleam/io

fn apply_multiplier(value: Int, maybe: Option(Int)) -> Int {
  case maybe {
    Some(m) -> value * m
    None -> value
  }
}

fn parse_positive(s: String) -> Result(Int, Nil) {
  case int.parse(s) {
    Ok(n) if n > 0 -> Ok(n)
    _ -> Error(Nil)
  }
}

pub fn main() {
  let maybe_multiplier = Some(2)

  case parse_positive("10") {
    Ok(n) ->
      let result = apply_multiplier(n, maybe_multiplier)
      io.println(int.to_string(result))
    Error(_) ->
      io.println("Invalid input")
  }
}

Expression blocks

Expression Blocks

error: Syntax error  
  ┌─ /Users/adi.salimgereyev/hello_world/src/hello_world.gleam:8:10  
  │  
8 │     let b = (1 + 2) * 3  
  │             ^ This parenthesis cannot be understood here

Hint: To group expressions in Gleam, use "{" and "}"; tuples are created with `#(` and `)`.

In Gleam you group expressions with {}. A block runs its expressions in order and evaluates to the last one. Names bound inside the block are scoped to that block.

pub fn area_of_square(size: Int) -> Int {
  let perimeter = {
    let doubled = size * 2
    doubled * 2
  }
  
  perimeter * size / 4
}

Blocks can change evaluation order. Wrap a subexpression in {} when you want it to run first:

// Default precedence gives 7
let a = 1 + 2 * 3

// Run 1 + 2 first, gives 9
let b = { 1 + 2 } * 3

Panic and assert

Assertions and panic

You already saw let assert earlier. It binds with a partial pattern and crashes if the value does not match. We use it for invariants we consider impossible to violate, and prefer returning a Result in library code so the caller can decide what to do.

panic crashes immediately. It is for code paths that must never run, such as unreachable branches or unimplemented cases. Add a message with as "..." so failures are clear:

import gleam/io
import gleam/int

pub fn describe(score: Int) {
  case score {
    s if s > 1000 -> io.println("High score!")
    s if s > 0 -> io.println("Still working on it")
    _ -> panic as "Scores should never be negative!"
  }
}

assert is another way to cause a panic. It checks that a boolean expression evaluates to True. Like the others, you can add a custom message with as. This replaces the generic message when the assertion fails. Do not use assert in applications or libraries. Keep it in tests:

pub fn add_test() {
  assert add(1, 2) == 3
  assert add(1, 2) < add(1, 3)
  assert add(6, 2) == add(2, 6) as "Addition should be commutative"
  assert add(2, 2) == 5
}

fn add(a: Int, b: Int) -> Int {
  a + b
}

Custom types

Custom types

Custom types let you model “this or that” choices with named constructors. A value of a custom type is one of its constructors, and you handle each case with case. This is a sum type, also called an algebraic data type.

pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
}

fn area(s: Shape) -> Float {
  case s {
    Circle(radius:) -> 3.14159 *. radius *. radius
    Rectangle(width:, height:) -> width *. height
  }
}

You define the type with type, give each variant a constructor, then pattern match by constructor.

A constructor that carries fields is called a record. You can read fields with dot syntax and update immutably with record update. This keeps data immutable and clear to read.

pub type User {
  User(name: String, age: Int)
}

pub fn main() {
  let u1 = User("Bea", 28)
  let u2 = User(..u1, age: 29)
  let name = u2.name
  
  io.println(name) // Bea
}

You can match on records to pull out fields or add guards. This keeps branching logic tight and local. The let form can destructure single-variant types, and case works for any variant set.

fn can_vote(u: User) -> Bool {
  case u {
    User(age: a) if a >= 18 -> True
    _ -> False
  }
}

Custom types can be generic. Write type variables in lowercase. This gives you reuse without losing static checks. Gleam’s own Option(a) and Result(a, e) follow this shape.

pub type Box(value) { 
  Box(value) 
}

fn map_box(x: Box(a), f: fn(a) -> b) -> Box(b) {
  case x { 
    Box(v) -> Box(f(v))
  }
}

When you need invariants, make the type opaque. Export the type, keep its constructors private, and expose smart constructors and getters. Callers can use the type but cannot forge or deconstruct it:

pub opaque type Id { 
  Id(value: String) 
}

pub fn from_string(s: String) -> Id {
  Id(s)
}

pub fn to_string(id: Id) -> String {
  id.value
}

Modules and imports

Generics

Advanced Gleam

Todo

Bit strings

escape sequences

Tail call optimisation

Phantom types

Labelled arguments

Labelled fields

Dict

Pairs

Sets

Option

Order

Iterator

Opaque types

Queue

Erlang and Javascript interop

Testing

Bytes and string builders

Base64

Regex

URI

Ecosystem

Installing custom packages

Creating and publishing a package

Gleam docker images

Simplifile package

Filepath: filepath

Ranges: ranger

Datetime: birl

Ad-hoc error type: snag

Json: gleam_json

gleam_crypto