Pipes
x |> f() is f(x). The piped value goes into the first argument of the
call on its right; any additional arguments come after. Pipes let you describe
a computation as a top-to-bottom flow rather than an inside-out chain of
nested calls — which is how most idiomatic Nomi reads.
The pipe operator
Section titled “The pipe operator”The simplest pipe is one stage. When the call has more than one argument, the piped value fills the first slot and the rest are written after:
fn double(x: Int): Int {
x * 2
}
fn add(x: Int, y: Int): Int {
x + y
}
fn main(): Int {
5 |> double() |> dbg
5 |> add(3) |> dbg
}
Longer chains stack one stage per line — the data flows top-to-bottom and each stage gets its own row:
fn main(): List<Int> {
[1, 2, 3]
|> Iter.map(|n| n * n)
|> Iter.to_list()
|> dbg
}
Pipes start from data
Section titled “Pipes start from data”Pipes read most clearly when the chain starts from concrete data and ends at where it’s used. Compare:
fn main(): Int {
// inside-out
dbg Iter.count(Iter.filter([1, 2, 3, 4, 5], |n| n > 2))
// pipeline — data first, transformations next, output last
[1, 2, 3, 4, 5]
|> Iter.filter(|n| n > 2)
|> Iter.count()
|> dbg
}
Both produce 3; only the second reads top-to-bottom.
The _ placeholder
Section titled “The _ placeholder”The piped value goes into the first argument by default. To pipe into a
different position, write _ where the piped value should land:
fn divide(x: Int, y: Int): Int {
x / y
}
fn main(): Int {
10 |> divide(100, _) |> dbg
}
That _ becomes divide(100, 10), which is 100 / 10.
Lambda stages
Section titled “Lambda stages”A pipe stage can be a lambda. Use this when the next step is a small inline expression rather than a named function call:
fn find_name(id: Int): Maybe<String> {
case id {
1 -> Some("Ada")
_ -> None
}
}
fn main(): Bool {
1
|> find_name()
|> |name| name == Some("Ada")
|> dbg
}
Keyword stages
Section titled “Keyword stages”Expression keywords can also be pipeline stages. Bare dbg inspects the
current value; try unwraps
Maybe and Result stages; assertions usually sit at the head of test
pipelines.
try short-circuits on None or Err:
fn main(): Maybe<Int> {
" 42 "
|> String.trim()
|> try String.to_int()
|> Some()
|> dbg
}
Assertions keep the subject visually complete:
test "pipeline result is true" {
assert " Ada Lovelace "
|> String.trim()
|> String.contains?("Ada")
}
if and case follow the same idea for branchy expressions:
fn main(): String {
"Ada"
|> if String.contains?("A") {
"initialed"
} else {
"plain"
}
|> dbg
Some("Ada")
|> case {
.Some(name) -> "named ${name}"
.None -> "missing"
}
|> dbg
}
The next chapter — Scalars & Strings — uses pipes from the first example onward.