Pipe Operator (|>)
The pipe operator provides ergonomic left-to-right function composition and data flow.
- Syntax:
value |> fn(args) - Desugaring:
value |> fn(a, b) becomes fn(value, a, b) - Associativity: left-to-right
- Precedence: between logical operators and bitwise operators
Basics
| Basic Pipeline |
|---|
| var result = data
|> filter(x => x > 0)
|> map(x => x * 2)
|> reduce((a, b) => a + b, 0)
|
Desugars to:
| Desugared Pipeline |
|---|
| var result = reduce(map(filter(data, x => x > 0), x => x * 2), (a, b) => a + b, 0)
|
Piping into calls vs identifiers
- If the right-hand side (RHS) is a call, the left value is prepended to its arguments:
| Piping Into a Call |
|---|
| // a |> f(b, c) => f(a, b, c)
var r = 10 |> add(1) // add(10, 1)
|
- If the RHS is a plain identifier expression (a function), it is called with the left value:
| Piping Into an Identifier |
|---|
| function inc(x any) any { return x + 1 }
var r = 5 |> inc // inc(5)
|
- Chaining works naturally:
| Chaining Functions |
|---|
| var r = 2 |> inc |> double |> square
|
Arrays and maps
You can pipe arrays through higher-order helpers:
| Array Helpers |
|---|
| function filter(arr any, pred function(any) bool) any { /*...*/ }
function mapArr(arr any, f function(any) any) any { /*...*/ }
function reduce(arr any, f function(any, any) any, init any) any { /*...*/ }
|
| Array Pipeline |
|---|
| var arr = [1, -2, 3, -4, 5]
var sum = arr
|> filter(x => x > 0)
|> mapArr(x => x * 10)
|> reduce((a, b) => a + b, 0)
// sum == 90
|
For maps, methods use receivers (e.g., m.values()), and the pipe does not auto-bind a receiver. Use one of these two patterns:
1) Call the method first, then pipe its result:
| Pattern 1: Call Method Then Pipe |
|---|
| var m = {"a": 2, "b": 3, "c": 5}
var total = m.values() |> reduce((a, b) => a + b, 0) // 10
var count1 = m.keys()
|> filter(k => len(k) == 1)
|> reduce((acc, _) => acc + 1, 0) // 3
|
2) Wrap the method in a helper or lambda so the piped value becomes its argument:
| Pattern 2: Helper Wrappers |
|---|
| function valuesOf(x any) any { return x.values() }
function keysOf(x any) any { return x.keys() }
var total2 = m |> valuesOf |> reduce((a, b) => a + b, 0) // 10
var count2 = m |> keysOf
|> filter(k => len(k) == 1)
|> reduce((acc, _) => acc + 1, 0) // 3
|
Multiline styles
Both styles are supported for multi-line chains.
- Leading-pipe style (pipe at line start):
| Leading-Pipe Style |
|---|
| var r = data
|> step1()
|> step2(42)
|> step3
|
- Trailing-pipe style (pipe at line end):
| Trailing-Pipe Style |
|---|
| var r = data |>
step1() |>
step2(42) |>
step3
|
In both cases, newlines directly after or before |> are treated as continuations of the same expression.
Precedence and associativity
- Precedence order (excerpt):
- logical (and/or)
- pipe (|>)
- bitwise (|, ^, &)
- comparisons, shifts, sum, product, exponent
- call/index have higher precedence than pipe
- Left-to-right association for chains:
| g(f(a)) Desugaring |
|---|
| var r = a |> f |> g // g(f(a))
var s = a |> ns.fn(1) // ns.fn(a, 1)
|
Tips
- Use parentheses when needed:
| Parentheses with Lambdas |
|---|
| var r = a |> (x => x * x) // call the lambda with a
|
- Piping into a method on an object requires a wrapper (lambda/helper) since methods use receivers.
Error handling
Pipe is purely syntactic. Type checking and runtime behavior are identical to the equivalent, desugared function calls.
Detailed examples
1) Array pipelines with filter/map/reduce
| Array Pipelines Example |
|---|
| package main
import fmt
function filter(arr any, pred function(any) bool) any {
var out = []
for x in arr { if pred(x) { out = append(out, x) } }
return out
}
function mapArr(arr any, f function(any) any) any {
var out = []
for x in arr { out = append(out, f(x)) }
return out
}
function reduce(arr any, f function(any, any) any, init any) any {
var acc = init
for x in arr { acc = f(acc, x) }
return acc
}
function main() {
var arr = [1, -2, 3, -4, 5]
var sum = arr
|> filter(x => x > 0)
|> mapArr(x => x * 10)
|> reduce((a, b) => a + b, 0)
fmt.Println(sum) // Output: 90
}
|
2) Piping into identifiers (functions) and calls
| Piping into Functions |
|---|
| package main
import fmt
function inc(x any) any { return x + 1 }
function double(x any) any { return x * 2 }
function square(x any) any { return x * x }
function main() {
var r = 2 |> inc |> double |> square
fmt.Println(r) // Output: 36
// Piping into a call prepends the left value to the argument list
function add(a any, b any) any { return a + b }
var s = 10 |> add(5) // add(10, 5)
fmt.Println(s) // Output: 15
}
|
3) Maps: values/keys with pipelines
| Map Pipelines |
|---|
| package main
import fmt
function reduce(arr any, f function(any, any) any, init any) any {
var acc = init
for x in arr { acc = f(acc, x) }
return acc
}
function main() {
var m = {"a": 2, "b": 3, "c": 5}
// Pattern 1: call method, then pipe the result
var total = m.values() |> reduce((a, b) => a + b, 0)
fmt.Println(total) // Output: 10
var count1 = m.keys()
|> filter(k => len(k) == 1)
|> reduce((acc, _) => acc + 1, 0)
fmt.Println(count1) // Output: 3
// Pattern 2: helper wrappers for methods
function valuesOf(x any) any { return x.values() }
function keysOf(x any) any { return x.keys() }
var total2 = m |> valuesOf |> reduce((a, b) => a + b, 0)
fmt.Println(total2) // Output: 10
}
|
4) Both multiline styles
| Multiline Pipelines |
|---|
| package main
import fmt
function inc(x any) any { return x + 1 }
function double(x any) any { return x * 2 }
function main() {
// Leading-pipe
var a = 5
|> inc()
|> double()
fmt.Println(a) // Output: 12
// Trailing-pipe
var b = 5 |>
inc() |>
double()
fmt.Println(b) // Output: 12
}
|