Overview
Gleam is a friendly language for building type-safe, scalable systems. It compiles to Erlang and JavaScript, letting you build for the BEAM (Erlang VM) or the browser and Node.js. Gleam brings static typing to the BEAM ecosystem while maintaining full interoperability with Erlang and Elixir libraries. The language features algebraic data types, pattern matching, generics, and a powerful type inference system that catches errors at compile time.
Created by Louis Pilfold, Gleam reached version 1.0 in March 2024. It combines the rock-solid reliability of the Erlang VM (known for powering telecom systems with 99.9999999% uptime) with a modern type system and developer experience. Gleam’s compiler provides helpful error messages, the language has no null values or exceptions, and its module system encourages clean, maintainable code. It integrates seamlessly with OTP patterns for building concurrent, fault-tolerant applications.
Installation
Package Managers
# macOS
brew install gleam
# Linux (asdf)
asdf plugin add gleam
asdf install gleam latest
asdf global gleam latest
# Linux (Nix)
nix-shell -p gleam
# Windows (scoop)
scoop install gleam
Prerequisites
# Erlang is required for BEAM target
brew install erlang # macOS
sudo apt install erlang # Ubuntu
# Node.js for JavaScript target (optional)
brew install node
Create Project
# New project
gleam new my_app
cd my_app
# Build and run
gleam run
# Run tests
gleam test
# Build only
gleam build
# Format code
gleam format
# Add dependency
gleam add gleam_json
gleam add gleam_http
Core Language
Types and Variables
// Let bindings (immutable)
let name = "Gleam"
let age: Int = 42
let pi: Float = 3.14159
// Type annotations are optional (inferred)
let greeting = "Hello, " <> name <> "!"
Basic Types
| Type | Example | Description |
|---|
Int | 42, 0xFF, 1_000 | Arbitrary precision integer |
Float | 3.14, 1.5e3 | 64-bit floating point |
String | "hello" | UTF-8 string |
Bool | True, False | Boolean |
Nil | Nil | Unit type |
List(a) | [1, 2, 3] | Linked list |
#(a, b) | #(1, "hi") | Tuple |
Result(a, e) | Ok(42), Error("fail") | Success or failure |
Option(a) | Some(42), None | Optional value |
BitArray | <<1, 2, 3>> | Binary data |
Functions
// Named function
fn add(a: Int, b: Int) -> Int {
a + b
}
// Public function
pub fn greet(name: String) -> String {
"Hello, " <> name <> "!"
}
// Anonymous function
let double = fn(x: Int) -> Int { x * 2 }
// Function with labeled arguments
pub fn connect(to host: String, on port: Int) -> Connection {
// host and port are used inside
// called as: connect(to: "localhost", on: 5432)
}
// Pipeline operator
"hello world"
|> string.uppercase
|> string.split(" ")
|> list.first
// => Ok("HELLO")
// Higher-order functions
let numbers = [1, 2, 3, 4, 5]
let doubled = list.map(numbers, fn(x) { x * 2 })
let evens = list.filter(numbers, fn(x) { x % 2 == 0 })
let sum = list.fold(numbers, 0, fn(acc, x) { acc + x })
Custom Types (Algebraic Data Types)
// Record type
pub type User {
User(name: String, email: String, age: Int)
}
// Create
let alice = User(name: "Alice", email: "alice@example.com", age: 30)
// Access fields
let name = alice.name
// Update (creates new value)
let updated = User(..alice, age: 31)
// Sum type (variants)
pub type Shape {
Circle(radius: Float)
Rectangle(width: Float, height: Float)
Triangle(base: Float, height: Float)
}
pub fn area(shape: Shape) -> Float {
case shape {
Circle(radius:) -> 3.14159 *. radius *. radius
Rectangle(width:, height:) -> width *. height
Triangle(base:, height:) -> base *. height /. 2.0
}
}
// Generic types
pub type Pair(a, b) {
Pair(first: a, second: b)
}
// Option type
pub type Option(a) {
Some(a)
None
}
Pattern Matching
// Case expression
pub fn describe(x: Int) -> String {
case x {
0 -> "zero"
1 -> "one"
n if n > 0 -> "positive"
_ -> "negative"
}
}
// Matching on custom types
pub fn to_string(result: Result(Int, String)) -> String {
case result {
Ok(value) -> "Success: " <> int.to_string(value)
Error(message) -> "Error: " <> message
}
}
// Multiple values
case x, y {
0, 0 -> "origin"
0, _ -> "y-axis"
_, 0 -> "x-axis"
_, _ -> "other"
}
// List patterns
case my_list {
[] -> "empty"
[only] -> "one element: " <> only
[first, ..rest] -> "starts with: " <> first
}
// Let assert (crash on no match)
let assert Ok(value) = get_data()
Error Handling with Result
import gleam/result
pub fn parse_and_double(input: String) -> Result(Int, String) {
// use keyword for early return on error (like ? in Rust)
use number <- result.try(int.parse(input))
use validated <- result.try(validate(number))
Ok(validated * 2)
}
// Result chaining
let output =
input
|> int.parse
|> result.map(fn(n) { n * 2 })
|> result.unwrap(0)
// result.try for monadic chaining
pub fn process() -> Result(String, Error) {
use user <- result.try(fetch_user(id))
use profile <- result.try(fetch_profile(user))
Ok(profile.name)
}
Lists and Iterators
import gleam/list
import gleam/iterator
// List operations
let nums = [3, 1, 4, 1, 5, 9]
list.map(nums, fn(x) { x * 2 }) // [6, 2, 8, 2, 10, 18]
list.filter(nums, fn(x) { x > 3 }) // [4, 5, 9]
list.fold(nums, 0, fn(acc, x) { acc + x }) // 23
list.sort(nums, int.compare) // [1, 1, 3, 4, 5, 9]
list.unique(nums) // [3, 1, 4, 5, 9]
list.flat_map(nums, fn(x) { [x, x] }) // [3, 3, 1, 1, ...]
list.chunk(nums, fn(x) { x > 3 }) // [[3, 1], [4], [1], [5, 9]]
list.zip(["a", "b"], [1, 2]) // [#("a", 1), #("b", 2)]
// Lazy iterators
iterator.range(1, 1000)
|> iterator.filter(fn(x) { x % 2 == 0 })
|> iterator.map(fn(x) { x * x })
|> iterator.take(5)
|> iterator.to_list
Modules and Imports
// src/my_app/user.gleam
import gleam/int
import gleam/string
import gleam/option.{type Option, None, Some}
// Qualified import
import gleam/list
// Aliased import
import gleam/io as console
// Type-only import
import my_app/types.{type User}
// Public types and functions are exported automatically
pub type Role {
Admin
Member
Guest
}
pub fn is_admin(role: Role) -> Bool {
role == Admin
}
Build and Dependencies
gleam.toml
name = "my_app"
version = "1.0.0"
target = "erlang" # or "javascript"
[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
gleam_json = ">= 1.0.0 and < 2.0.0"
gleam_http = ">= 3.5.0 and < 4.0.0"
gleam_otp = ">= 0.10.0 and < 1.0.0"
mist = ">= 1.0.0 and < 2.0.0"
wisp = ">= 0.14.0 and < 1.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
Build Commands
| Command | Description |
|---|
gleam new project_name | Create new project |
gleam build | Compile project |
gleam run | Build and run |
gleam test | Run tests |
gleam format | Format all files |
gleam add package_name | Add dependency |
gleam remove package_name | Remove dependency |
gleam docs build | Generate documentation |
gleam publish | Publish to Hex.pm |
gleam export erlang-shipment | Create release |
Configuration
Using Environment and Config
import gleam/erlang/os
pub fn get_port() -> Int {
os.get_env("PORT")
|> result.then(int.parse)
|> result.unwrap(3000)
}
Advanced Usage
OTP Integration (Erlang target)
import gleam/otp/actor
import gleam/erlang/process
// Define actor message type
pub type Message {
Increment
GetCount(reply_with: process.Subject(Int))
}
// Start actor
pub fn start() -> Result(process.Subject(Message), actor.StartError) {
actor.start(0, fn(message, count) {
case message {
Increment -> actor.continue(count + 1)
GetCount(client) -> {
process.send(client, count)
actor.continue(count)
}
}
})
}
Web Server with Wisp
import wisp.{type Request, type Response}
import mist
pub fn handle_request(req: Request) -> Response {
case wisp.path_segments(req) {
[] -> wisp.ok() |> wisp.string_body("Welcome!")
["api", "users"] -> handle_users(req)
["api", "users", id] -> handle_user(req, id)
_ -> wisp.not_found()
}
}
fn handle_users(req: Request) -> Response {
case req.method {
http.Get -> {
let users = get_all_users()
wisp.ok() |> wisp.json_body(encode_users(users))
}
http.Post -> {
use body <- wisp.require_json(req)
// process body
wisp.created()
}
_ -> wisp.method_not_allowed([http.Get, http.Post])
}
}
pub fn main() {
wisp.configure_logger()
let assert Ok(_) =
handle_request
|> wisp.mist_handler(secret_key)
|> mist.new
|> mist.port(3000)
|> mist.start_http
process.sleep_forever()
}
Testing
import gleeunit
import gleeunit/should
pub fn main() {
gleeunit.main()
}
pub fn add_test() {
add(2, 3)
|> should.equal(5)
}
pub fn greet_test() {
greet("World")
|> should.equal("Hello, World!")
}
pub fn parse_error_test() {
int.parse("not a number")
|> should.be_error
}
Troubleshooting
| Problem | Solution |
|---|
| Type mismatch | Check function signatures; Gleam’s type inference is strict |
| Missing Erlang | Install Erlang/OTP for BEAM target; check erl on PATH |
| Dependency conflict | Update gleam.toml version ranges; run gleam deps download |
| Pattern match not exhaustive | Add missing cases; use _ for catch-all |
let assert crash | Handle errors with case or result.try instead |
| FFI type errors | Check external function declarations match Erlang/JS types |
| Build cache issues | Delete build/ directory and rebuild |
| JavaScript target issues | Not all BEAM libraries work on JS; check package compatibility |