Pipelines

|> feeds the value on the left into the call on the right.

result = data
    |> filter(pred)
    |> map(transform)
    |> sort()

Equivalent to sort(map(filter(data, pred), transform)), and considerably easier to read: the steps are in the order they happen, one per line, and adding a stage is adding a line rather than finding the right nesting depth.

It binds loosest

|> has the lowest precedence of any operator, so a pipeline stage can contain arithmetic, comparison and boolean logic without parentheses:

xs |> filter(fn(x) x > 2 and x < 10) |> len()

A leading |> also continues a statement across a newline, which is why the multi-line form above needs no continuation marker.

With ai

This is where pipelines and the language's purpose meet. The piped value becomes the prompt input:

dates = text
    |> ai "Extract all dates"
    |> ai "Format as ISO 8601"

Each stage is a model call whose input is the previous stage's output. The same shape works with types, so a pipeline can narrow from text to structure:

report = raw_email
    |> ai "Extract the complaint"
    |> ai[Severity] "How severe is this?"

The argument position

The piped value is passed as the first argument, which is why the standard library consistently takes its subject first - filter(list, pred), replace(s, from, to), insert(map, k, v). That consistency is what makes arbitrary functions pipeable.

For a function whose subject is not first, use a lambda:

value |> fn(v) other(config, v)

Parallel

Swap map for pmap and the stage runs in parallel:

summaries = docs
    |> pmap(fn(d) ai "Summarize: {d}")
    |> filter(fn(s) len(s) > 0)