Skip to content

Commit 55306ac

Browse files
committed
lambdas for higher-order scalar functions
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
1 parent 345317b commit 55306ac

10 files changed

Lines changed: 671 additions & 14 deletions

File tree

‎vortex-array/src/expr/bound_expression.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use vortex_session::VortexSession;
1717
use crate::dtype::DType;
1818
use crate::expr::Expression;
1919
use crate::expr::display::DisplayTreeExpr;
20+
use crate::expr::expression::unbound_variable;
2021
use crate::expr::scope::Scope;
2122
use crate::expr::traversal::TraversalOrder;
2223
use crate::expr::traversal::pre_order_visit_down;
@@ -310,6 +311,9 @@ impl Expression {
310311
if self.is_root() {
311312
return Ok(BoundExpression::new_root(scope.root().clone()));
312313
}
314+
if let Expression::Variable(name) = self {
315+
return Err(unbound_variable(name));
316+
}
313317

314318
let children: Vec<_> = self
315319
.children()
@@ -318,7 +322,7 @@ impl Expression {
318322
.try_collect()?;
319323
let scalar_fn = self
320324
.as_scalar()
321-
.vortex_expect("root was handled above, so this is a scalar node");
325+
.vortex_expect("leaves were handled above, so this is a scalar node");
322326
BoundExpression::try_new(scalar_fn.clone(), children)
323327
}
324328
}

‎vortex-array/src/expr/display.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ impl DisplayTreeNode for Expression {
6969
match self {
7070
Expression::Scalar { scalar_fn, .. } => scalar_fn.signature().child_name(index),
7171
Expression::Root => unreachable!("the scope root has no children"),
72+
Expression::Variable(_) => unreachable!("a variable has no children"),
7273
}
7374
}
7475

7576
fn fmt_tree_node(&self, f: &mut Formatter<'_>) -> fmt::Result {
7677
match self {
7778
Expression::Scalar { scalar_fn, .. } => Display::fmt(scalar_fn, f),
7879
Expression::Root => write!(f, "{ROOT_DISPLAY}"),
80+
Expression::Variable(name) => write!(f, "@{name}"),
7981
}
8082
}
8183
}

‎vortex-array/src/expr/expression.rs‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ use std::hash::Hash;
1010
use std::sync::Arc;
1111

1212
use itertools::Itertools;
13+
use vortex_error::VortexError;
1314
use vortex_error::VortexExpect;
1415
use vortex_error::VortexResult;
1516
use vortex_error::vortex_ensure;
17+
use vortex_error::vortex_err;
1618

1719
use crate::dtype::DType;
20+
use crate::dtype::FieldName;
1821
use crate::expr::display::DisplayTreeExpr;
1922
use crate::expr::traversal::TraversalOrder;
2023
use crate::expr::traversal::pre_order_visit_down;
@@ -26,10 +29,12 @@ const NO_CHILDREN: &[Expression] = &[];
2629

2730
/// A node in a Vortex expression tree.
2831
///
29-
/// Most nodes are a scalar function applied to child expressions. [`Expression::Root`] is the scope
30-
/// itself: a language primitive rather than a registered function, because its dtype comes from the
31-
/// scope rather than from children and it is not executable. A [`ScalarFnVTable`] can answer neither
32-
/// of those, so `Root` is a variant instead.
32+
/// Most nodes are a scalar function applied to child expressions. The other two are leaves that name
33+
/// a value rather than compute one: [`Expression::Root`], the scope itself, and
34+
/// [`Expression::Variable`], a parameter of an enclosing lambda. They are language primitives rather
35+
/// than registered functions, because their dtype comes from where they appear rather than from
36+
/// children and they are not executable. A [`ScalarFnVTable`] can answer neither of those, so they
37+
/// are variants instead.
3338
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3439
pub enum Expression {
3540
/// A scalar function applied to child expressions.
@@ -41,6 +46,12 @@ pub enum Expression {
4146
},
4247
/// The full scope of the expression evaluation.
4348
Root,
49+
/// A parameter of an enclosing lambda, read by name.
50+
///
51+
/// Building the [`Lambda`](crate::expr::Lambda) replaces each variable of its parameters with a
52+
/// field of the lambda's row, so a variable left over is not a parameter of any enclosing
53+
/// lambda. It fails to bind and cannot be serialized.
54+
Variable(FieldName),
4455
}
4556

4657
impl Expression {
@@ -73,7 +84,7 @@ impl Expression {
7384
pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
7485
match self {
7586
Self::Scalar { scalar_fn, .. } => Some(scalar_fn),
76-
Self::Root => None,
87+
Self::Root | Self::Variable(_) => None,
7788
}
7889
}
7990

@@ -101,7 +112,7 @@ impl Expression {
101112
pub fn children(&self) -> &[Expression] {
102113
match self {
103114
Self::Scalar { children, .. } => children.as_slice(),
104-
Self::Root => NO_CHILDREN,
115+
Self::Root | Self::Variable(_) => NO_CHILDREN,
105116
}
106117
}
107118

@@ -125,6 +136,14 @@ impl Expression {
125136
);
126137
Ok(Self::Root)
127138
}
139+
Self::Variable(name) => {
140+
vortex_ensure!(
141+
children.is_empty(),
142+
"Expression arity mismatch: variable expects 0 children but got {}",
143+
children.len()
144+
);
145+
Ok(Self::Variable(name.clone()))
146+
}
128147
Self::Scalar { scalar_fn, .. } => {
129148
vortex_ensure!(
130149
scalar_fn.signature().arity().matches(children.len()),
@@ -144,6 +163,7 @@ impl Expression {
144163
pub fn return_dtype(&self, scope: &DType) -> VortexResult<DType> {
145164
match self {
146165
Self::Root => Ok(scope.clone()),
166+
Self::Variable(name) => Err(unbound_variable(name)),
147167
Self::Scalar {
148168
scalar_fn,
149169
children,
@@ -164,6 +184,7 @@ impl Expression {
164184
pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
165185
match self {
166186
Self::Root => write!(f, "$"),
187+
Self::Variable(name) => write!(f, "@{name}"),
167188
Self::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
168189
}
169190
}
@@ -249,6 +270,11 @@ impl Expression {
249270
}
250271
}
251272

273+
/// The error for a variable that no enclosing lambda declares.
274+
pub(crate) fn unbound_variable(name: &FieldName) -> VortexError {
275+
vortex_err!("variable `{name}` is not a parameter of an enclosing lambda")
276+
}
277+
252278
/// The default display implementation for expressions uses the 'SQL'-style format.
253279
impl Display for Expression {
254280
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {

‎vortex-array/src/expr/exprs.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,23 @@ pub fn bound_get_item(field: impl Into<FieldName>, child: BoundExpression) -> Bo
146146
.vortex_expect("get-item expressions must reference a field in the child dtype")
147147
}
148148

149+
// ---- Variable ----
150+
151+
/// Creates an expression that reads the parameter `name` of an enclosing lambda.
152+
///
153+
/// It is only meaningful inside a body passed to [`Lambda::new`](crate::expr::Lambda::new), which
154+
/// rewrites it into a field of the lambda's row. A variable that no enclosing lambda declares fails
155+
/// to bind.
156+
///
157+
/// ```rust
158+
/// # use vortex_array::expr::{Lambda, checked_add, lit, var};
159+
/// let (lambda, _) = Lambda::new(["x"], checked_add(var("x"), lit(1_i32))).unwrap();
160+
/// assert_eq!(lambda.to_string(), "(x) -> ($.x + 1i32)");
161+
/// ```
162+
pub fn var(name: impl Into<FieldName>) -> Expression {
163+
Expression::Variable(name.into())
164+
}
165+
149166
// ---- VariantGet ----
150167

151168
/// Creates an expression that extracts a path from a Variant expression.

0 commit comments

Comments
 (0)