Repository navigation
Conversation
Merging this PR will degrade performance by 10.74%3 warnings, 1 note
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
4b7dd4a to
700462a
Compare
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Summary
Adds lambdas, the function arguments of higher-order scalar functions such as
list_transform(#10503). A lambda is a list of parameter names and a body that reads them through variables. A function that takes a lambda keeps it in its options, binds it once when its node is bound, and runs the bound lambda over values it makes from its inputs.Variables
var("x")buildsExpression::Variable("x"): a leaf, like$, that reads the parameterxof the enclosing lambda.@x, so@x(a parameter) and$.x(a field of the input) never look alike.@xinto$.x, a field of the struct of the lambda's inputs (see below), soBoundExpressionis unchanged.Lambdas
Lambdais the lambda as written:params()andbody(), anExpression.Lambda::newrejects a repeated parameter, and a body that reads from outside the lambda:$or a column, another lambda's parameter, or the scan'srow_idx()androw_count(). Constants are fine, such as the literal set inx -> list_contains([2, 3], x).bind(param_dtypes)turns the lambda into an ordinary expression over a struct of its inputs, one field per parameter: each@xbecomes$.x, and the body is bound to that struct. It returns aBoundLambda.return_dtype(param_dtypes)gives the body's dtype over the same struct, without binding it, the wayExpression::return_dtypedoes for a whole expression.BoundLambdais the lambda bound:params()with their dtypes,body(), an ordinaryBoundExpressionover the struct,dtype(), the body's, andis_bound_to(param_dtypes).BoundLambda::evaluate(args, len)puts one array per parameter in that struct and applies the bound body to it. The arrays must have the dtypes the lambda is bound to. It binds nothing, and like any applied expression, the result is computed later.is_infallible(), on both, says whether nothing in the body can fail, so a function knows whether the body may run on values whose results are never read, such as dictionary values no row uses.Using a lambda in a function
A function's options are an enum of the lambda as written and as bound. Its children are its ordinary inputs.
ScalarFnVTable::bind_options(options, arg_dtypes)is new, and does nothing by default. It runs whenever a bound node is built, and whenever aScalarFnArrayis built from its children, beforereturn_dtype. That's where a function binds its lambda, once, soexecutefinds it bound.bind_optionsagain, possibly with other dtypes, so a function checks that options already bound still fit, and errors if they don't. No rewrite today changes the dtype a lambda is bound to.Expressionholds the written lambda, and aBoundExpressionor an array holds the bound one.replace, field pruning and pushdown see an ordinary node and never look inside the lambda.Not supported yet
x -> x + $.a.Testing
cargo +nightly-2026-09-10 fmt -p vortex-array -p vortex-layoutcargo clippy -p vortex-array -p vortex-layout --all-targets --all-features -- -D warningscargo nextest run -p vortex-array -p vortex-layout(the full suites), including a vortex-layout test that a lambda can't readrow_idx()cargo test --doc -p vortex-array -- lambda exprs::varFollow-up