Lambdas¶
The meta-language provides lambda expressions — anonymous single-argument functions written inline as arguments to map or filter. (reduce's second argument is a bare reducer identifier, not a lambda — see Reducers.) A lambda lets you describe a per-element transformation or predicate without declaring a named smelt.define. Lambdas chain naturally with the pipe operator |>.
Lambdas are a meta-world construct. They are evaluated entirely at compile time and never reach the database engine.
Syntax: fn x => body¶
fnis a reserved keyword that introduces the lambda.IDENTis the single parameter name, bound for use insideEXPR.=>is the lambda arrow (distinct from thename => valuenamed-argument separator that appears outsidefnbodies).EXPRis any meta-evaluable expression: asmelt.<path>(...)call, a HOF call, a pipe chain, a list literal, or an arithmetic/comparison expression involving the bound name.
Example — double each element:
-- examples/meta_hofs/models/pipe_rewrite.sql
-- fn c => c * 2 transforms every element of the list.
SELECT [1, 2, 3] |> filter(fn c => c > 0) |> map(fn c => c * 2)
-- Engine sees the meta-evaluated result: SELECT 2, 4, 6
Example — compose with and_all:
-- reduce([true, false, true], and_all) reduces a Boolean list.
-- The lambda here is inside the filter:
SELECT
reduce(
filter([true, false, true], fn b => b),
and_all
)
Multiple parameters¶
The parenthesised form fn (a, b, …) => body declares a lambda with two or more parameters:
- The parameter list is parenthesised and comma-separated; trailing commas are permitted.
k = 0is rejected withLambdaZeroParameters— a zero-parameter lambda has no use case in the closed HOF surface.- All
kparameter names must be distinct within the lambda; a duplicate emitsLambdaDuplicateParameterat the second occurrence. - The parenthesised form is also accepted for arity
k = 1(fn (x) => body); the two single-arg surfaces are equivalent. fn a, b => body(no parens, comma between parameters) is a parse error at the first comma.
The lambda's type is Lambda<(T_1, …, T_k), U> where T_1 … T_k are the parameter types inferred from the HOF call site and U is the body's synthesised return type.
Multi-arg lambdas and the v1 HOF surface. map and filter require arity-1 lambdas (Lambda<T, Boolean> and Lambda<T, U> respectively). Passing a multi-arg lambda to either emits LambdaArityMismatch. No v1 HOF accepts a multi-arg lambda yet — multi-arg lambdas parse and type-check but become useful only when a multi-list HOF such as zip_with is added in a future version.
Example — correct (arity 1):
Example — arity mismatch (emits LambdaArityMismatch):
-- map requires a Lambda<T, U> of arity 1; (a, b) is arity 2
-- ← LambdaArityMismatch: map expects arity 1; found arity 2
SELECT map([1, 2, 3], fn (a, b) => a + b)
Where lambdas are allowed¶
A lambda is only valid as a positional argument to a HOF (map, filter). Writing a lambda anywhere else emits LambdaInForbiddenPosition anchored at the fn keyword:
| Position | Allowed? |
|---|---|
Second argument to map(xs, fn x => ...) |
Yes |
Second argument to filter(xs, fn x => ...) |
Yes |
List element [fn x => x, ...] |
No — LambdaInForbiddenPosition |
Named-argument value p => fn x => x |
No — LambdaInForbiddenPosition |
smelt.define parameter or return type |
No — Lambda<T, U> is not a user-writable annotation |
| Top-level expression | No — LambdaInForbiddenPosition |
Parameter scoping¶
Inside the lambda body, the bound name resolves before any wider scope. The resolution order (from scoping.md) is:
- Lambda parameter — wins over everything inside the body.
smelt.definefunction parameters.- CTE columns visible at the reference site.
- FROM-scope columns from
TableExprparameters.
A lambda parameter may shadow a same-named smelt.define parameter or column. The shadow is intentional (lexical scoping); the inner binding wins. To reach a shadowed outer name, assign it to an intermediate variable before the lambda, or use a qualified reference.
Example — lambda parameter shadows outer binding:
-- `c` inside the lambda refers to the element, not any outer `c`.
SELECT map([1, 2, 3], fn c => c + 10)
What a lambda body can reference¶
| What | Allowed? |
|---|---|
| The lambda's own parameter | Yes |
Enclosing smelt.define parameters |
Yes |
Meta-only outer-scope values (List<T>, smelt.config.var('x') results) |
Yes |
| SQL columns only available at Data-World runtime | No — UnknownIdentifier at the bare reference |
Lambdas capture the compile-time meta-world. Runtime SQL columns do not exist at meta-evaluation time and cannot be referenced inside a lambda body.
LSP support¶
- Hover on the lambda parameter inside the body shows the parameter's bound type (supplied by the surrounding HOF's
T). - Goto-definition on the lambda parameter inside the body resolves to the parameter's binding occurrence in the lambda head (
fntoken). - Completion inside a lambda body offers the bound parameter as the first identifier completion.
Diagnostic codes¶
LambdaInForbiddenPosition
When it fires: A fn x => body lambda appears outside a HOF positional argument position.
Message: lambda is only valid as an argument to a higher-order function
Fires at: the fn keyword.
Example:
-- ← LambdaInForbiddenPosition: lambda is not a HOF argument here
SELECT fn x => x + 1 FROM smelt.sources.raw.users
What to fix: Move the lambda inside a call to map or filter. If you want to apply a transformation to every element of a list, use map(xs, fn x => ...). If you need a named transformation, define it with smelt.define.
LambdaArityMismatch
When it fires: The lambda's parameter count does not match the arity required by the surrounding HOF call site. For example, map and filter require arity 1; passing a two-parameter lambda emits this diagnostic.
Message: {hof} expects a lambda of arity {expected}; found arity {actual}
Fires at: the lambda's parameter list.
Example:
-- map requires arity 1; fn (a, b) => a + b is arity 2
-- ← LambdaArityMismatch: map expects arity 1; found arity 2
SELECT map([1, 2, 3], fn (a, b) => a + b)
What to fix: Match the lambda's parameter count to what the HOF requires. map and filter take an arity-1 lambda. If you need a multi-arg lambda, you need a HOF that accepts one — no v1 HOF does this yet.
LambdaZeroParameters
When it fires: A lambda's parameter list is empty: fn () => body.
Message: lambda must declare at least one parameter
Fires at: the empty parameter list ().
Example:
-- ← LambdaZeroParameters: lambda must declare at least one parameter
SELECT map([1, 2, 3], fn () => 42)
What to fix: Add at least one parameter. If the body does not use the parameter, name it _ by convention: fn _ => 42.
LambdaDuplicateParameter
When it fires: The same parameter name appears more than once in a single lambda's parameter list.
Message: parameter '{name}' already appears in this lambda's parameter list
Fires at: the second (duplicate) occurrence of the parameter name.
Example:
-- ← LambdaDuplicateParameter: 'x' already appears in this lambda's parameter list
SELECT map([1, 2, 3], fn (x, x) => x)
What to fix: Give each parameter a distinct name. If you intended two separate bindings, choose different identifiers for each slot.
LambdaResultTypeMismatch
When it fires: The lambda body's synthesised type is incompatible with what the surrounding HOF requires.
Message: {hof} requires lambda result {expected}; found {actual}
Fires at: the body expression.
Example:
-- filter requires Lambda<T, Boolean>; returning an integer instead
-- ← LambdaResultTypeMismatch: filter requires Boolean; found INTEGER
SELECT filter([1, 2, 3], fn c => c + 1)
What to fix: Adjust the body expression to match the HOF's required return type. For filter, the body must evaluate to a Boolean predicate (e.g. c > 0, c IS NOT NULL). For map, the body may return any type, but all elements must share the same result type.