Higher-Order Functions¶
The meta-language provides three built-in higher-order functions (HOFs): map, filter, and reduce. HOFs operate on List<T> values at compile time and produce new List<U> values or reduced fragments that splice into SQL. Each HOF takes a lambda or a reducer as its second argument.
All three HOFs are meta-world only. Their inputs and outputs are compile-time values; the database engine sees the already-reduced SQL fragments, never the HOF calls themselves.
The three HOFs at a glance¶
| HOF | Signature | Returns |
|---|---|---|
map |
(xs: List<T>, f: Lambda<T, U>) -> List<U> |
A new list of the same length as xs, where each element is the result of applying f |
filter |
(xs: List<T>, p: Lambda<T, Boolean>) -> List<T> |
A sub-list of xs (in original order), keeping only elements for which p returns TRUE |
reduce |
(xs: List<T>, r) -> OutputSort |
A single fragment — the result of folding xs left-to-right using reducer r |
HOFs accept exactly two positional arguments and zero named arguments. The => that appears in a lambda body (fn x => expr) is part of the lambda syntax; it is not a named argument.
map¶
map(xs, fn x => body) applies a lambda to every element of a list and returns a new list of the same length.
Type signature:
Worked example — drawn from examples/meta_hofs/:
-- examples/meta_hofs/models/pipe_rewrite.sql
-- map(xs, fn c => c * 2) doubles every element.
-- [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)
Behaviour:
- Walks xs left-to-right exactly once.
- The lambda body is type-checked once (parametrically, under the HOF's T) and evaluated once per element.
- Order is preserved: map([a, b, c], f) produces [f(a), f(b), f(c)].
- The result list has the same length as xs. map never drops or duplicates elements.
Editor support: hovering over a map(...) call shows List<U> with U resolved to the lambda body's inferred type.
filter¶
filter(xs, fn x => predicate) keeps only the elements of a list for which the predicate returns TRUE.
Type signature:
Worked example:
-- Keep only the positive numbers.
-- After compile, the filtered list [1, 2, 3] (all positives) is produced.
SELECT [1, 2, 3] |> filter(fn c => c > 0)
Behaviour:
- The predicate body must synthesise to Boolean. A non-Boolean predicate emits LambdaResultTypeMismatch.
- An element is kept if and only if the predicate is TRUE. A predicate that evaluates to Unknown drops the element silently (no error).
- Order is preserved: retained elements appear in their original order.
- The result is a List<T> — the same element type as xs.
Editor support: hovering over a filter(...) call shows List<T> with T resolved to the input element type.
reduce¶
reduce(xs, r) folds a list into a single fragment using one of the seven registered reducers.
Type signature:
where r is a bare reducer identifier from the closed registry, and OutputSort is the reducer's declared output sort.
Worked example — and_all (reduces a list of Boolean expressions with AND):
-- examples/meta_hofs/models/and_all_predicates.sql
-- reduce([true, false, true], and_all) folds left: true AND false AND true
SELECT reduce([true, false, true], and_all)
Worked example — plus_chain (reduces a list of numeric expressions with +):
-- examples/meta_hofs/models/comma_sep_select_list.sql
-- reduce([1, 2, 3], plus_chain) folds left: 1 + 2 + 3
SELECT reduce([1, 2, 3], plus_chain)
Behaviour:
- The reducer identifier is resolved at type-check time from the closed registry. It is not a value-bearing expression — you cannot pass a variable as the reducer argument.
- If xs is non-empty, the result is the reducer's left-fold over xs in original order.
- If xs is empty, the result depends on the reducer's declared identity element. See Reducers for the full table.
Editor support: hovering over the reducer name in reduce(xs, here) shows the reducer's input element type, output sort, and empty-list identity (or "no identity").
Reserved names¶
The names map, filter, and reduce are reserved at the meta-namespace level. A smelt.define function declared with one of these names emits HofNameShadowed at the declaration. Reserved names also cannot be used as smelt.define parameter names.
This reservation is workspace-wide — it applies even if your workspace has no models that use HOFs.
Pipe shorthand¶
The three HOFs chain naturally with the pipe operator |>:
-- Pipe-chained form
SELECT [1, 2, 3] |> filter(fn c => c > 0) |> map(fn c => c * 2)
-- Equivalent un-piped form
SELECT map(filter([1, 2, 3], fn c => c > 0), fn c => c * 2)
Both forms are semantically identical. Pipe is purely syntactic sugar; the type checker desugars it before checking.
Diagnostic codes¶
HofExpectsLambda
When it fires: The second argument to map or filter is not a lambda.
Message: {hof} expects a lambda; found {actual type}
Fires at: the second argument expression.
Example:
-- ← HofExpectsLambda: second arg must be a lambda, not an identifier
SELECT map([1, 2, 3], some_function)
What to fix: Replace the second argument with a fn x => body lambda. For example: map([1, 2, 3], fn c => c + 1). Named smelt.define functions cannot be passed as HOF arguments; write the transformation inline as a lambda.
HofExpectsReducer
When it fires: The second argument to reduce is not a bare reducer identifier from the closed registry.
Message: reduce expects a reducer; found {actual}
Fires at: the second argument span.
Example:
-- ← HofExpectsReducer: 'my_reducer' is not a registered reducer name
SELECT reduce([1, 2, 3], my_reducer)
What to fix: Replace the second argument with one of the seven registered reducer names: comma_sep, and_all, or_any, union_all, intersect_all, plus_chain, or concat. See Reducers for the input type and output sort of each. You cannot define your own reducers in v1; the registry is closed.
HofNameShadowed
When it fires: A smelt.define function is declared with the name map, filter, or reduce.
Message: {name} is a reserved higher-order function name
Fires at: the declaration's name token.
Example:
-- ← HofNameShadowed: 'map' is a reserved HOF name
smelt.define map(xs: List<Expr<Integer>>) -> SelectItems<Scalar> ...
What to fix: Rename your smelt.define function. Choose a name that does not conflict with the three reserved HOF names (map, filter, reduce) or any of the seven reserved reducer names.