Skip to content

Commit aafd2d3

Browse files
committed
lambdas for higher-order scalar functions
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
1 parent 2e41681 commit aafd2d3

14 files changed

Lines changed: 758 additions & 8 deletions

File tree

‎vortex-array/src/arrays/scalar_fn/array.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ impl Array<ScalarFn> {
110110
Self::validate_children_len(&slots, len)?;
111111

112112
let arg_dtypes = Self::arg_dtypes(&slots);
113+
let scalar_fn = scalar_fn.bind_options(&arg_dtypes)?;
113114
let dtype = scalar_fn.return_dtype(&arg_dtypes)?;
114115

115116
Ok(unsafe { Self::new_from_validated_slots(scalar_fn, dtype, slots, len) })

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

Lines changed: 6 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;
@@ -138,6 +139,7 @@ impl BoundExpression {
138139
.iter()
139140
.map(|child| child.dtype().clone())
140141
.collect_vec();
142+
let scalar_fn = scalar_fn.bind_options(&arg_dtypes)?;
141143
let dtype = scalar_fn.return_dtype(&arg_dtypes)?;
142144

143145
Ok(Self::Scalar {
@@ -310,6 +312,9 @@ impl Expression {
310312
if self.is_root() {
311313
return Ok(BoundExpression::new_root(scope.root().clone()));
312314
}
315+
if let Expression::Variable(name) = self {
316+
return Err(unbound_variable(name));
317+
}
313318

314319
let children: Vec<_> = self
315320
.children()
@@ -318,7 +323,7 @@ impl Expression {
318323
.try_collect()?;
319324
let scalar_fn = self
320325
.as_scalar()
321-
.vortex_expect("root was handled above, so this is a scalar node");
326+
.vortex_expect("leaves were handled above, so this is a scalar node");
322327
BoundExpression::try_new(scalar_fn.clone(), children)
323328
}
324329
}

‎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: 31 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
33+
/// name 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
35+
/// rather than registered functions, because their dtype comes from where they appear rather than
36+
/// from children and they are not executable. A [`ScalarFnVTable`] can answer neither of those, so
37+
/// they 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,11 @@ pub enum Expression {
4146
},
4247
/// The full scope of the expression evaluation.
4348
Root,
49+
/// A parameter of the enclosing lambda, read by name.
50+
///
51+
/// Binding a [`Lambda`](crate::expr::Lambda) turns its variables into reads of the struct of
52+
/// its inputs. Anywhere else a variable fails to bind, and it is not serializable yet.
53+
Variable(FieldName),
4454
}
4555

4656
impl Expression {
@@ -73,7 +83,7 @@ impl Expression {
7383
pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
7484
match self {
7585
Self::Scalar { scalar_fn, .. } => Some(scalar_fn),
76-
Self::Root => None,
86+
Self::Root | Self::Variable(_) => None,
7787
}
7888
}
7989

@@ -101,7 +111,7 @@ impl Expression {
101111
pub fn children(&self) -> &[Expression] {
102112
match self {
103113
Self::Scalar { children, .. } => children.as_slice(),
104-
Self::Root => NO_CHILDREN,
114+
Self::Root | Self::Variable(_) => NO_CHILDREN,
105115
}
106116
}
107117

@@ -125,6 +135,14 @@ impl Expression {
125135
);
126136
Ok(Self::Root)
127137
}
138+
Self::Variable(name) => {
139+
vortex_ensure!(
140+
children.is_empty(),
141+
"Expression arity mismatch: variable expects 0 children but got {}",
142+
children.len()
143+
);
144+
Ok(Self::Variable(name.clone()))
145+
}
128146
Self::Scalar { scalar_fn, .. } => {
129147
vortex_ensure!(
130148
scalar_fn.signature().arity().matches(children.len()),
@@ -144,6 +162,7 @@ impl Expression {
144162
pub fn return_dtype(&self, scope: &DType) -> VortexResult<DType> {
145163
match self {
146164
Self::Root => Ok(scope.clone()),
165+
Self::Variable(name) => Err(unbound_variable(name)),
147166
Self::Scalar {
148167
scalar_fn,
149168
children,
@@ -164,6 +183,7 @@ impl Expression {
164183
pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
165184
match self {
166185
Self::Root => write!(f, "$"),
186+
Self::Variable(name) => write!(f, "@{name}"),
167187
Self::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
168188
}
169189
}
@@ -249,6 +269,11 @@ impl Expression {
249269
}
250270
}
251271

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

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,25 @@ pub fn bound_get_item(field: impl Into<FieldName>, child: BoundExpression) -> Bo
149149
.vortex_expect("get-item expressions must reference a field in the child dtype")
150150
}
151151

152+
// ---- Variable ----
153+
154+
/// Creates an expression that reads the parameter `name` of the enclosing lambda.
155+
///
156+
/// It is only meaningful in the body of a [`Lambda`](crate::expr::Lambda). Anywhere else it fails
157+
/// to bind.
158+
///
159+
/// ```rust
160+
/// # use vortex_array::dtype::{DType, Nullability, PType};
161+
/// # use vortex_array::expr::{checked_add, lit, var};
162+
/// let expr = checked_add(var("x"), lit(1_i32));
163+
/// assert_eq!(expr.to_string(), "(@x + 1i32)");
164+
/// // Outside a lambda body, nothing gives `x` a value.
165+
/// assert!(expr.bind(&DType::Primitive(PType::I32, Nullability::NonNullable)).is_err());
166+
/// ```
167+
pub fn var(name: impl Into<FieldName>) -> Expression {
168+
Expression::Variable(name.into())
169+
}
170+
152171
// ---- VariantGet ----
153172

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

0 commit comments

Comments
 (0)