| name | gleam |
| description | Write idiomatic Gleam code for the BEAM VM and JavaScript. Use when working with .gleam files, Gleam projects, or when the user mentions Gleam, BEAM, OTP actors, or type-safe functional programming. |
Gleam
Gleam is a statically typed functional programming language for the Erlang VM (BEAM) and JavaScript. It combines Erlang's legendary fault tolerance with a modern type system that catches errors at compile time.
Core Philosophy: Explicitness Over Implicitness
Gleam rejects implicit behavior in favor of transparency:
- No implicit casting:
Intcannot be treated asFloat. Conversions are explicit functions. - No operator overloading:
+for Int,+.for Float. Reading code reveals types immediately. - No exceptions for control flow: Errors are values (
Resulttype), not thrown exceptions. - No null: Absence is modeled with
Option(a).
// Explicit conversion required
let x: Int = 42
let y: Float = int.to_float(x) // Not just `x + 1.0`
// Operators are type-specific
let sum_int = 1 + 2 // Int addition
let sum_float = 1.0 +. 2.0 // Float addition (note the dot)
Naming Conventions (Parser-Enforced)
| Entity | Case | Example | Notes |
|---|---|---|---|
| Variables | snake_case | user_id, is_valid |
Must start lowercase |
| Functions | snake_case | get_user, parse_json |
Verb-noun phrases |
| Modules | snake_case | gleam/io, my_app/user |
Matches filename |
| Types | PascalCase | User, HttpRequest |
Must start uppercase |
| Constructors | PascalCase | Ok, Error, Some |
Same as types |
| Constants | snake_case | max_retries |
SCREAMING_SNAKE also common |
Hard constraint: The parser enforces casing. type user fails. let User = fails.
Essential Syntax
Variables and Immutability
// Immutable binding
let name = "Gleam"
// Variable shadowing (idiomatic for transformations)
let user = " alice "
let user = string.trim(user)
let user = string.uppercase(user)
// Previous `user` values are discarded
Functions
// Private function
fn add(a: Int, b: Int) -> Int {
a + b
}
// Public function with documentation
/// Greets a user by name.
pub fn greet(name: String) -> String {
"Hello, " <> name <> "!"
}
// Anonymous function (closure)
let double = fn(x) { x * 2 }
// Labelled arguments (for clarity at call site)
pub fn create_user(name name: String, age age: Int) -> User {
User(name: name, age: age)
}
// Called with labels
create_user(name: "Alice", age: 30)
The Pipe Operator |>
Gleam's signature feature—data flows left to right through transformation pipelines:
" hello world "
|> string.trim
|> string.uppercase
|> string.replace(" ", "_")
// => "HELLO_WORLD"
// Subject always goes to FIRST argument
// Use capture `_` for other positions
10
|> int.add(5, _) // Passes 10 as second arg
Pattern Matching with case
The primary control flow construct. Replaces if/else, switch, and try/catch:
case value {
0 -> "zero"
1 -> "one"
n if n > 0 -> "positive"
_ -> "negative"
}
// Destructuring
case user {
User(name: n, age: a) if a >= 18 -> "Adult: " <> n
User(name: n, ..) -> "Minor: " <> n
}
// Matching on Result
case fetch_user(id) {
Ok(user) -> render(user)
Error(NotFound) -> "User not found"
Error(DbError(msg)) -> "Database error: " <> msg
}
// Tuple matching (multiple values)
case role, is_logged_in {
Admin, True -> render_admin()
User, True -> render_user()
_, False -> render_login()
}
The use Expression
Flattens callbacks and monadic chains. The code below use becomes a callback function:
// Without use (pyramid of doom)
result.try(step1(), fn(a) {
result.try(step2(a), fn(b) {
Ok(a + b)
})
})
// With use (flat and readable)
use a <- result.try(step1())
use b <- result.try(step2(a))
Ok(a + b)
// Resource management pattern
use file <- with_file("data.txt")
// File automatically closed when block ends
Data Types
Primitives
// Bool
let flag: Bool = True // or False
// Int (arbitrary precision on BEAM)
let count: Int = 1_000_000
let hex: Int = 0xFF
// Float (64-bit)
let pi: Float = 3.14159
let sci: Float = 1.0e-10
// String (UTF-8)
let greeting: String = "Hello"
let multiline = "Line 1
Line 2"
let concat = "Hello" <> " " <> "World" // Not +
Custom Types (Algebraic Data Types)
// Record (product type)
pub type User {
User(
id: Int,
name: String,
email: String,
)
}
// Union (sum type) - replaces enums
pub type ConnectionState {
Disconnected
Connecting(attempt: Int)
Connected(ip: String, port: Int)
}
// Opaque type (encapsulation)
pub opaque type Email {
Email(String)
}
pub fn new_email(s: String) -> Result(Email, String) {
case string.contains(s, "@") {
True -> Ok(Email(s))
False -> Error("Invalid email")
}
}
Option and Result
import gleam/option.{type Option, Some, None}
import gleam/result
// Option for nullable values
pub type Profile {
Profile(
name: String,
bio: Option(String), // May be absent
)
}
// Result for fallible operations
pub fn divide(a: Int, b: Int) -> Result(Int, String) {
case b {
0 -> Error("Division by zero")
_ -> Ok(a / b)
}
}
Error Handling (Railway Oriented)
Errors are values, not exceptions. Chain with result.try or use:
pub type AppError {
NotFound
InvalidInput(String)
DbError(String)
}
pub fn get_user_email(id: Int) -> Result(String, AppError) {
use user <- result.try(fetch_user(id))
use profile <- result.try(fetch_profile(user.id))
Ok(profile.email)
}
// Map errors to unified type
pub fn load_config() -> Result(Config, AppError) {
read_file("config.json")
|> result.map_error(fn(_) { InvalidInput("Config not found") })
|> result.try(parse_json)
}
Modules and Imports
// Import module
import gleam/io
import gleam/list
// Import with alias
import gleam/string as str
// Import specific items
import gleam/option.{type Option, Some, None}
// Qualified access
io.println("Hello")
list.map([1, 2, 3], fn(x) { x * 2 })
Common Gotchas
- Operators are typed:
+for Int,+.for Float,<>for String - No implicit truthiness:
if listis invalid; useif list.length(x) > 0 - Exhaustive matching:
casemust handle ALL variants - No return keyword: Last expression is the return value
- Guards are limited: Only comparisons/type checks, no function calls
- String concat is
<>: Not+ - No null: Use
Option(a)for optional values
Quick Reference
// List operations
[1, 2, 3] |> list.map(fn(x) { x * 2 }) // [2, 4, 6]
[1, 2, 3] |> list.filter(fn(x) { x > 1 }) // [2, 3]
[1, 2, 3] |> list.fold(0, fn(acc, x) { acc + x }) // 6
// Option operations
Some(42) |> option.map(fn(x) { x * 2 }) // Some(84)
None |> option.unwrap(0) // 0
// Result operations
Ok(42) |> result.map(fn(x) { x * 2 }) // Ok(84)
Error("fail") |> result.unwrap(0) // 0
Advanced Topics
- Type system: See TYPES.md
- Control flow and use: See CONTROL-FLOW.md
- Error handling: See ERRORS.md
- Modules and architecture: See ARCHITECTURE.md
- TDD/BDD/ATDD methodology: See TESTING.md
- Idioms and anti-patterns: See PATTERNS.md
Running Gleam
gleam new my_project # Create project
gleam run # Run project
gleam test # Run tests
gleam build # Build
gleam add gleam_json # Add dependency