Skip to content

Pipes

The pipe operator |> chains transformations left-to-right, making data flow readable.

// Pipe the left side as the first argument to the right side
const result = "hello" |> toUpperCase
// Compiles to: toUpperCase("hello")
const result = users
|> filter(.active)
|> map(.name)
|> sort
|> join(", ")

Compiles to:

const result = join(sort(map(filter(users, (u) => u.active), (u) => u.name)), ", ");

The piped version reads like a recipe: take users, filter, map, sort, join.

When the piped value isn’t the first argument, use _:

const result = 5 |> add(3, _)
// Compiles to: add(3, 5)
const result = value
|> multiply(2)
|> add(10, _)
|> toString

For simple field access or comparisons, use .field:

todos |> Array.filter(.done == false)
todos |> Array.map(.text)
users |> Array.sortBy(.name)

.field creates an implicit closure. .done == false is shorthand for fn(t) t.done == false.

For anything more complex, use fn(x):

todos |> Array.map(fn(t) Todo(..t, done: !t.done))

Pipes work with any function, including methods accessed via imports:

import trusted { map, filter, reduce } from "ramda"
const total = orders
|> filter(.status == "complete")
|> map(.amount)
|> reduce(fn(sum, n) sum + n, 0, _)

Need to inspect a value mid-pipeline without breaking the chain? Use tap:

const result = users
|> Array.filter(.active)
|> tap(Console.log) // logs filtered users, passes them through
|> Array.map(.name)
|> Array.sort

tap calls the function you give it (for side effects like logging), then returns the original value unchanged. It compiles to an IIFE that calls the function and returns the value.

You can pipe a value directly into match to combine pipelines with pattern matching:

const label = price |> match {
_ when _ < 10 -> "cheap",
_ when _ < 100 -> "moderate",
_ -> "expensive",
}

This is equivalent to match price { ... } but lets you keep the pipeline flowing:

const message = response.status
|> match {
200..299 -> "success",
404 -> "not found",
500..599 -> "server error",
s -> `unexpected: ${s}`,
}

It works at the end of a chain too:

const label = product
|> effectivePrice
|> match {
_ when _ < 10 -> "cheap",
_ when _ < 100 -> "moderate",
_ -> "expensive",
}

x |> match { ... } compiles identically to match x { ... }. It is pure syntax sugar for pipeline ergonomics.

Pipes shine when you have a sequence of transformations. They replace:

Instead ofUse
c(b(a(x)))x |> a |> b |> c
x.map(...).filter(...).reduce(...)x |> map(...) |> filter(...) |> reduce(...)
Temporary variablesDirect piping

Floe has three arrow-like operators:

fn(x) anonymous closures fn(a) a + 1
-> match arms / types Ok(x) -> x, fn(string) -> number
|> pipe data data |> transform

Each has a distinct purpose. No ambiguity.