Skip to content

feat: add lambda expression support#710

Open
limameml wants to merge 3 commits intosubstrait-io:mainfrom
limameml:limame.malainine/add-lambda-support
Open

feat: add lambda expression support#710
limameml wants to merge 3 commits intosubstrait-io:mainfrom
limameml:limame.malainine/add-lambda-support

Conversation

@limameml
Copy link

This PR is to add support for the Lambda Expression

Summary

  • Add lambda expression support to substrait-java core and isthmus
  • Implement Type.Func for function types
  • Add Expression.Lambda and lambda parameter references
  • Full proto round-trip support
  • Calcite (isthmus) integration (single-level lambdas only)

Test plan

  • Added LambdaExpressionRoundtripTest in core
  • Added LambdaExpressionTest in isthmus

closes #687

@CLAassistant
Copy link

CLAassistant commented Feb 24, 2026

CLA assistant check
All committers have signed the CLA.

@benbellick benbellick self-requested a review February 24, 2026 17:00
Copy link
Member

@benbellick benbellick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only just started looking at this but flushing a few comments just to get the ball rolling. On the whole this is looking good though, nice work!

List<Type> paramTypes = parameters().fields();
Type returnType = body().getType();

return Type.withNullability(false).func(paramTypes, returnType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is setting nullability false correct here? I am pretty sure func can be nullable, e.g. func?<i32 -> i32>.

Also, probably will be good to make a test captures this then.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the substrait-go implementation: the GetType function for lambda returns Nullability: types.NullabilityRequired

func (l *Lambda) GetType() types.Type {
	return &types.FuncType{
		Nullability:    types.NullabilityRequired,
		ParameterTypes: l.Parameters.Types,
		ReturnType:     l.Body.GetType(),
	}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I am thinking about this right now but that may have been wrong in substrait-go 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a spec error. I raised an issue in upstream here.

Mind leaving a comment there referencing this issue as a TODO so we remember to fix it once it gets resolved?

}
}

@Value.Immutable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay if you feel confident in your LambdaInvocation work, but considering the size of this PR, it will make it easier both on you and the reviewer to separate out the Lambda work and LambdaInvocation work into two separate PRs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll handle LambdaInvocation in a separate pr

private final Type.Struct rootType;
private final ProtoTypeConverter protoTypeConverter;
private final ProtoRelConverter protoRelConverter;
private final List<Type.Struct> lambdaParameterStack = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a stack here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to access lambdaIndex = lambdaParameterStack.size() - 1 - stepsOut here: lambdaParameters = lambdaParameterStack.get(lambdaIndex); when building the Lambda parametre ref that's why I didn't use a stack

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. Java's stack does extend Vector though so you still get the get method. I'm not so particular on this one.

The only thing I will say is I do think that the whole stack parameter stuff can be confusing for people without a little bit of PL experience, which is why I pushed @Slimsammylim to encapsulate some of that logic and put it in docstring comments. I could see a case for encapsulating this logic in a local private class, though it probably makes more sense to do this only if this logic comes up again.

I expected to see something like this logic elsewhere, as you need to do it to do validation when building lambdas. Though I didn't find anything like this. Does the builder for lambdas in this PR actually validate that the lambda is semantically correct?

.setStruct(protoLambda.getParameters())
.build());

lambdaParameterStack.add(parameters);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then we can use the standard stack push here

try {
body = from(protoLambda.getBody());
} finally {
lambdaParameterStack.remove(lambdaParameterStack.size() - 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the standard stack pop here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support lambdas

3 participants