Skip to content

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
1
2
3
4
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
1
2
3
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
1
2
3
4
5
6
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
1
2
3
4
5
6
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
1
2
3
4
5
6
7
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
1
2
3
4
var r = data
  |> step1()
  |> step2(42)
  |> step3
  • Trailing-pipe style (pipe at line end):
Trailing-Pipe Style
1
2
3
4
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
}