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.

Leave a Comment

Your email address will not be published. Required fields are marked *