Pipe Operator |>¶
The pipe operator is MOL's defining feature — the only language with |> and auto-tracing.
Basic Syntax¶
The value on the left becomes the first argument of the function on the right:
Why Pipes?¶
Pipes make data flow visible and readable.
Extra Arguments¶
Pass additional arguments after the piped value:
"hello world" |> split(" ") -- split("hello world", " ")
"hello" |> replace("l", "L") -- replace("hello", "l", "L")
42 |> add(8) -- add(42, 8)
Auto-Tracing¶
The Killer Feature
When a pipe chain has 3 or more stages, MOL automatically prints a trace table showing the input, each stage's execution time, and output type.
Output:
┌─ Pipeline Trace ──────────────────────────────────────
│ 0. input ─ Text(" Hello, IntraMind! ")
│ 1. trim 0.0ms → Text("Hello, IntraMind!")
│ 2. lower 0.0ms → Text("hello, intramind!")
│ 3. split(..) 0.0ms → List<2 strs>
└─ 3 steps · 0.0ms total ───────────────────────────
Why 3+ Stages?¶
- 2 stages = simple transformation, trace would be noise
- 3+ stages = multi-step pipeline, visibility is valuable
Disable Tracing¶
Or in code, short chains (< 3 stages) never trace:
User Functions in Pipes¶
Your functions work seamlessly in pipes:
define double(x)
return x * 2
end
define add_ten(x)
return x + 10
end
show 5 |> double |> add_ten |> double
Lambdas in Pipes¶
Use inline functions (fn(x) -> expr) directly in pipes — no separate function needed:
let nums be [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-- Filter, map, reduce — all inline
show nums |>
filter(fn(x) -> x > 5) |>
map(fn(x) -> x * 2) |>
reduce(fn(a, b) -> a + b, 0)
-- 80
Common Pipe Patterns¶
let data be [10, 20, 30, 40, 50]
-- Get items > 25, doubled
show data |> where(fn(x) -> x > 25) |> select(fn(x) -> x * 2)
-- [60, 80, 100]
-- Find first item > 25
show data |> where(fn(x) -> x > 25) |> first
-- 30
-- Count matching items
show data |> where(fn(x) -> x > 25) |> len
-- 3
-- Sum of filtered items
show data |> where(fn(x) -> x > 25) |> sum_list
-- 120
Working with Objects¶
let users be [
{"name": "Alice", "age": 25, "active": true},
{"name": "Bob", "age": 35, "active": false},
{"name": "Charlie", "age": 30, "active": true}
]
-- Smart mode: extract properties and filter by truthy fields
show users |> filter("active") |> pluck("name")
-- ["Alice", "Charlie"]
-- Sort by property
show users |> sort_by("age") |> map("name")
-- ["Alice", "Charlie", "Bob"]
┌─ Pipeline Trace ──────────────────────────────────────
│ 0. input ─ Number(5)
│ 1. double 0.0ms → Number(10)
│ 2. add_ten 0.0ms → Number(20)
│ 3. double 0.0ms → Number(40)
└─ 3 steps · 0.0ms total ───────────────────────────
Named Pipelines¶
Define reusable pipeline blocks:
pipeline clean_text(text)
let result be text |> trim |> lower
guard len(result) > 0 : "Empty after cleaning"
return result
end
-- Use as regular function
show clean_text(" HELLO ") -- "hello"
-- Use in pipes
show " WORLD " |> clean_text
Real-World: RAG Pipeline¶
-- Full RAG pipeline in 4 lines
let doc be Document("knowledge.txt", content)
let index be doc |> chunk(512) |> embed("model-v1") |> store("kb")
let answer be retrieve("What is AI?", "kb", 3) |> think("answer")
guard answer.confidence > 0.5 : "Low confidence"
Pipe + Debug¶
Use tap for debugging without breaking the chain:
Use display to print and pass through:
How It Works Internally¶
The pipe operator is parsed as a PipeChain AST node. At evaluation time:
- The first expression is evaluated as the initial value
- Each subsequent stage receives the previous result as its first argument
- If stages ≥ 3, a trace buffer records timing and type info for each step
- After execution, the trace is printed to stderr
- The final value is returned
This means pipes have zero overhead when tracing is disabled — they desugar to nested function calls.