Pipe Operator |>¶
The meta-language provides the pipe operator |>, which lets you write a chain of HOF calls in left-to-right reading order instead of nesting them inside-out. The pipe operator works particularly well with lambdas: xs |> filter(fn x => …) |> map(fn x => …) reads as a natural left-to-right pipeline.
The pipe operator is meta-world only: both sides evaluate at compile time. No |> expression ever reaches the database engine.
Syntax and semantics¶
|> inserts the left-hand side as the first argument to the call on the right-hand side. Everything else about the call — its type, its result, its diagnostic anchoring — is identical to writing the un-piped form directly. Pipe is purely syntactic sugar and is desugared before type-checking.
Example — single pipe:
-- [1, 2, 3] |> filter(fn c => c > 0)
-- desugars to: filter([1, 2, 3], fn c => c > 0)
SELECT [1, 2, 3] |> filter(fn c => c > 0)
Example — chained pipes (from examples/meta_hofs/):
-- examples/meta_hofs/models/pipe_rewrite.sql
-- [1, 2, 3] |> filter(fn c => c > 0) |> map(fn c => c * 2)
-- desugars to: map(filter([1, 2, 3], fn c => c > 0), fn c => c * 2)
SELECT [1, 2, 3] |> filter(fn c => c > 0) |> map(fn c => c * 2)
Associativity and precedence¶
|> is left-associative with the lowest precedence among meta-language operators:
Pipe has lower precedence than function calls, spread (...), and field access. It has higher precedence than statement separators (;) — pipe never crosses a statement boundary.
Pipe is meta-world only¶
|> evaluates only in the meta world. Both the left-hand expression and the right-hand call are compile-time meta-language constructs. Attempting to use pipe inside a Data-World position (for example, inside a WHERE predicate) emits PipeInDataPosition.
This is a deliberate scope cut. Extending |> into Data-World SQL (pipe-SQL) is a separate design that affects the planner and query executor, not just the meta-language.
The RHS must be a call¶
The right-hand side of |> must syntactically be a function call — one of:
- A HOF call: map(args), filter(args), reduce(args)
- A smelt.<path>(args) call
- Any other function call: f(args)
A non-call right-hand side (x |> y, x |> 3 + 4) emits PipeRhsNotCall at the RHS span.
LSP support¶
- Hover on a pipe expression shows the result type of the equivalent un-piped call — identical to hovering on the desugared form.
- Pipe expressions participate in the same frame-stack tracing as their un-piped equivalents. A diagnostic that fires inside a piped HOF chain carries the same frames as the un-piped version.
Diagnostic codes¶
PipeRhsNotCall
When it fires: The right-hand side of |> is not a function call expression.
Message: pipe right-hand side must be a function call
Fires at: the RHS span.
Example:
What to fix: The right-hand side of |> must be a call: f(args), map(fn x => ...), smelt.my_fn(args), etc. If you mean to pass the left-hand value as the only argument, write f(left) without pipe, or add empty parentheses: f() with LHS |> f().
PipeInDataPosition
When it fires: A pipe expression (LHS |> f(...)) appears in a Data-World grammar position — for example, inside a WHERE predicate or as the right-hand side of a SQL binary operator.
Message: |> is meta-only; use SQL composition in this position
Fires at: the |> token.
Example:
SELECT id
FROM smelt.sources.raw.users
-- ← PipeInDataPosition: |> is meta-only; cannot use it in WHERE
WHERE active = true AND [1, 2, 3] |> filter(fn c => c > 0)
What to fix: Pipe is only valid in meta-world positions (where the result is a list or a meta-evaluable expression). Move the pipe chain to a compile-time context — for example, bind the result to a smelt.define parameter or use it where a List<T> is expected. For WHERE-clause predicate composition from a list, use the and_all or or_any reducer directly.