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
- Language tour
- Official website
- Discord server
- Github
- Cheat sheets for:
- Awesome Gleam resource list
- Standard library documentation
- Gleam package index
gleam.tomlfile reference- Command line reference (for
gleamcommand) - Gleam’s threads on the Erlang Forums
- Gleam discussions on Github
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.
-
Clone the repository:
git clone https://github.com/gleam-lang/gleam cd gleam -
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
-
-
Build the compiler:
cargo install --path gleam-binThis 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:
Intfor whole numbersFloatfor 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
}