Gleam Pipelines
The pipe operator |> passes the result of one expression as the first argument to the next function. Pipelines transform deeply nested, inside-out function calls into a clear left-to-right reading sequence — like assembly line steps where each station receives the output of the previous one.
The Problem Without Pipes
Nested functions — read from inside out:
──────────────────────────────────────────────────
let result = string.uppercase(
string.trim(
string.replace(
" hello world ", "world", "gleam"
)
)
)
// "HELLO GLEAM"
You must read from the innermost function outward to understand the flow. The first operation applied is buried deepest inside the nesting.
The Same Code With Pipes
With pipe — read left to right, top to bottom:
──────────────────────────────────────────────────
let result =
" hello world "
|> string.replace("world", "gleam")
|> string.trim
|> string.uppercase
// "HELLO GLEAM"
Pipeline Assembly Line
──────────────────────────────────────────────────
" hello world "
│
▼ string.replace("world", "gleam")
" hello gleam "
│
▼ string.trim
"hello gleam"
│
▼ string.uppercase
"HELLO GLEAM"
How |> Works
The pipe operator places its left-hand value as the first argument of the right-hand function:
value |> function(other_args)
// is exactly the same as:
function(value, other_args)
Pipe Expansion
──────────────────────────────────────────────────
"hello" |> string.replace("l", "r")
↕
string.replace("hello", "l", "r")
→ "herro"
Single-Argument Pipes
let numbers = [3, 1, 4, 1, 5, 9, 2, 6]
let result =
numbers
|> list.sort(int.compare)
|> list.reverse
|> list.take(3)
// [9, 6, 5] — top 3 numbers in descending order
Step Trace
──────────────────────────────────────────────────
[3, 1, 4, 1, 5, 9, 2, 6]
|> list.sort → [1, 1, 2, 3, 4, 5, 6, 9]
|> list.reverse→ [9, 6, 5, 4, 3, 2, 1, 1]
|> list.take(3)→ [9, 6, 5]
Pipes with Anonymous Functions
let total =
[100, 200, 300, 400]
|> list.filter(fn(n) { n > 150 })
|> list.map(fn(n) { n + 50 })
|> list.fold(0, fn(acc, n) { acc + n })
// filter → [200, 300, 400]
// map → [250, 350, 450]
// fold → 1050
Mixing Named and Anonymous Functions in a Pipeline
import gleam/string
import gleam/list
pub fn normalize_tags(raw: String) -> List(String) {
raw
|> string.split(",")
|> list.map(string.trim)
|> list.map(string.lowercase)
|> list.filter(fn(s) { string.length(s) > 0 })
|> list.sort(string.compare)
}
// normalize_tags(" Gleam , Erlang, , Elixir ")
// → ["elixir", "erlang", "gleam"]
Pipeline Variable Assignment
Assign intermediate pipeline results to variables for debugging or reuse:
let raw_tags = " Gleam , Erlang, , Elixir "
let split = raw_tags |> string.split(",")
let trimmed = split |> list.map(string.trim)
let lowered = trimmed |> list.map(string.lowercase)
let clean = lowered |> list.filter(fn(s) { s != "" })
let sorted = clean |> list.sort(string.compare)
When Pipe Doesn't Fit
The pipe operator places the value as the FIRST argument. When a function takes the data as its second argument, use an anonymous function wrapper:
// list.fold takes the list as first arg — pipe works directly:
[1,2,3] |> list.fold(0, fn(acc, n) { acc + n })
// string.replace takes the string as first arg — pipe works:
"hello" |> string.replace("h", "j")
// When you need the piped value in a different position:
42
|> fn(n) { string.pad_left(int.to_string(n), 5, "0") }
// "00042"
Practical Example — Invoice Processing
import gleam/list
import gleam/io
type LineItem {
LineItem(name: String, quantity: Int, unit_price: Float)
}
pub fn item_total(item: LineItem) -> Float {
int.to_float(item.quantity) *. item.unit_price
}
pub fn invoice_total(items: List(LineItem)) -> Float {
items
|> list.map(item_total)
|> list.fold(0.0, fn(acc, t) { acc +. t })
}
pub fn apply_gst(total: Float) -> Float {
total *. 1.18
}
pub fn main() {
let order = [
LineItem("Pen", 10, 5.0),
LineItem("Book", 3, 150.0),
LineItem("Bag", 1, 800.0)
]
let final_amount =
order
|> invoice_total
|> apply_gst
io.debug(final_amount) // (50 + 450 + 800) × 1.18 = 1534.0
}
Key Points
Pipeline Essentials
──────────────────────────────────────────────────
1. |> passes left value as first arg to right function
2. Reads top-to-bottom, left-to-right
3. Eliminates deeply nested function calls
4. Works with named functions and anonymous functions
5. Pipe value must match the function's first parameter type
6. Use fn(x) { ... } wrapper when position doesn't match
7. Assigns naturally: let result = value |> step1 |> step2
Pipelines are the style of Gleam code you see everywhere in professional projects. They communicate intent clearly, make refactoring easy — just add or remove a step — and reveal the data transformation story from start to finish.
