Skip to content
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
29ebf33
First try at lambda support
Slimsammylim Jan 20, 2026
86704d5
Changing code to use the builder pattern
Slimsammylim Jan 20, 2026
bdae181
Adding an extra guard for invalid plans when we try to resolve types
Slimsammylim Jan 21, 2026
4b6ef3b
Changing the builder struct to not use interface{}
Slimsammylim Jan 21, 2026
941b5a5
Adding clarifying comments
Slimsammylim Jan 21, 2026
c63aca6
Making visitors recursive
Slimsammylim Jan 21, 2026
a76e2ac
Handling when a body is a lambda when resolving param types
Slimsammylim Jan 21, 2026
9b64203
Adding tests for basic functions
Slimsammylim Jan 22, 2026
2d8ac51
More test coverage
Slimsammylim Jan 22, 2026
86b2046
Validation Error test cases
Slimsammylim Jan 22, 2026
49ce3d2
Update expr/field_reference.go
Slimsammylim Jan 22, 2026
e77547b
Update expr/lambda_test.go
Slimsammylim Jan 22, 2026
1c283a7
Update expr/lambda_test.go
Slimsammylim Jan 22, 2026
b787bb3
Update expr/lambda_test.go
Slimsammylim Jan 22, 2026
6db3eb1
Update expr/lambda_test.go
Slimsammylim Jan 22, 2026
9aa140d
Using URNs not URIs
Slimsammylim Jan 22, 2026
30b650e
Using the builder interface in builder.go
Slimsammylim Jan 22, 2026
0966f42
Using a full plan for TestLambdaWithFunctionExprFromProto
Slimsammylim Jan 22, 2026
b05f2e0
Removing redundant getters and unnecessary tests
Slimsammylim Jan 23, 2026
c639da5
Editing tests and comments to make it clear that a lambda can have no…
Slimsammylim Jan 23, 2026
87590bd
Removing unnecessary comments
Slimsammylim Jan 23, 2026
153cdae
Fixing inconsistent testcase
Slimsammylim Jan 23, 2026
3f908c7
Adding coverage for Equals() for field references
Slimsammylim Jan 23, 2026
4384248
Adding basic validation checks for lambdas in ExprFromProto
Slimsammylim Jan 23, 2026
fc1e121
Update expr/builder.go
Slimsammylim Jan 26, 2026
816b375
Update expr/builder.go
Slimsammylim Jan 26, 2026
d4b0ee5
Update expr/builder.go
Slimsammylim Jan 26, 2026
122ff0c
Update expr/builder.go
Slimsammylim Jan 26, 2026
70af94a
Making body an input to constructor
Slimsammylim Jan 26, 2026
e311de6
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Jan 26, 2026
dddb8b7
Adding functions for pushing and popping lambda context
Slimsammylim Jan 26, 2026
3d63665
Adding a lambdaParamRefBuilder
Slimsammylim Jan 27, 2026
b53d6e3
Adding a comment to tell users that only the builders guarentee valid…
Slimsammylim Jan 27, 2026
61676b1
Adding todo comments for issue #189
Slimsammylim Jan 27, 2026
1ef7d48
Removing lambda validation from ExprFromProto
Slimsammylim Jan 27, 2026
5e79979
Moving LambdaParamRef for clarity
Slimsammylim Jan 27, 2026
cf7f196
Fixing incorrect comment
Slimsammylim Jan 27, 2026
5428ccb
Update expr/builder.go
Slimsammylim Jan 28, 2026
372262b
Update expr/builder.go
Slimsammylim Jan 28, 2026
1b6409d
Removing mention of private lambda context in ExprBuilder
Slimsammylim Jan 28, 2026
81737e4
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Jan 28, 2026
bd32484
Adding comments for pushLambdaContext and popLambdaContext
Slimsammylim Jan 28, 2026
0ff2e44
Adding a helper for getting params in Build()
Slimsammylim Jan 28, 2026
dfab2c9
Adding a comment for parameter nullability requirement
Slimsammylim Jan 28, 2026
4cc2b39
removing unused code
Slimsammylim Jan 28, 2026
9132eeb
Moving JSONs used for tests into seperate test data folder
Slimsammylim Jan 28, 2026
a9dd781
Adding a test for a lambda as a parameter with outside references
Slimsammylim Jan 28, 2026
2bc6d62
Using two spaces instead of tabs in JSON files
Slimsammylim Jan 28, 2026
fbf73a2
Update expr/lambda.go
Slimsammylim Jan 29, 2026
628ee37
Update expr/builder.go
Slimsammylim Jan 29, 2026
08f3d29
Update expr/lambda.go
Slimsammylim Jan 29, 2026
884fad0
Adding FuncType as the type for lambda bodies
Slimsammylim Jan 29, 2026
0b5805b
Adding FuncType as the type for lambda bodies
Slimsammylim Jan 29, 2026
e1a53f0
Adding FuncType as the type for lambda bodies
Slimsammylim Jan 29, 2026
cfe657c
Realized we can assume that all lambdaParamRefBuilder refs are Struct…
Slimsammylim Jan 29, 2026
5c08270
Update expr/builder.go
Slimsammylim Jan 30, 2026
a2e003c
Only using full plans for lambda tests
Slimsammylim Jan 30, 2026
03c6ab6
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Jan 30, 2026
c35ae76
lambdaParamRefs only use StructFieldRef
Slimsammylim Jan 30, 2026
c8ee7d7
Update expr/lambda_test.go
Slimsammylim Jan 30, 2026
41422d1
Update expr/lambda_test.go
Slimsammylim Jan 30, 2026
e9c27a8
Update expr/lambda_test.go
Slimsammylim Jan 30, 2026
f56ba94
Update expr/lambda_test.go
Slimsammylim Jan 30, 2026
51dd93e
Adding literal package to lambda tests
Slimsammylim Jan 30, 2026
cd63be4
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Jan 30, 2026
9fd7d68
Adding comments to clarify the desired lambda in tests
Slimsammylim Jan 30, 2026
7a58a30
Better error message for getting lambda param types
Slimsammylim Jan 30, 2026
05e9370
Updating lambda tests
Slimsammylim Jan 30, 2026
d1e76bf
Update expr/builder.go
Slimsammylim Jan 30, 2026
a75b372
Update expr/builder.go
Slimsammylim Jan 30, 2026
3717967
Update expr/builder.go
Slimsammylim Jan 30, 2026
2b898a8
Update expr/builder.go
Slimsammylim Jan 30, 2026
6e04873
Update expr/builder.go
Slimsammylim Jan 30, 2026
17b813c
Moving func type tests to types_test.go
Slimsammylim Jan 30, 2026
b3fe321
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Jan 30, 2026
1becf60
Update go.mod
Slimsammylim Jan 30, 2026
8835174
Updating substrait version
Slimsammylim Jan 30, 2026
e299dab
Merge branch 'main' of github.com:substrait-io/substrait-go into slim…
Slimsammylim Feb 3, 2026
01e9339
chore: update substrait-protobuf and grammars
vbarua Feb 3, 2026
e5ad38f
chore: regenerate ANTLR parsers
vbarua Feb 3, 2026
8ab57c1
refactor: remove non-existent type visitor methods
vbarua Feb 3, 2026
72381cc
refactor: nullability is no longer attached to Arg Context
vbarua Feb 3, 2026
8f1a7c2
refactor: re-use and improve int and float type handling
vbarua Feb 3, 2026
a6eec4b
refactor: nullability is now attached to type
vbarua Feb 3, 2026
02b5dc0
refactor: more robust DataTypeVisitor
vbarua Feb 3, 2026
d658430
test: update error messages from new grammar
vbarua Feb 4, 2026
eb5ee31
chore: go fmt
vbarua Feb 4, 2026
84c3897
Merge vbarua/v0.79.0-update into slim/adding-lambda-support
Slimsammylim Feb 4, 2026
7c44877
Updating extension parser to support FuncType
Slimsammylim Feb 5, 2026
e5ffbdf
Merge branch 'main' of github.com:substrait-io/substrait-go into slim…
Slimsammylim Feb 5, 2026
eb6db27
Updated failing test
Slimsammylim Feb 5, 2026
2cf1e0b
new test cases that use transform func
Slimsammylim Feb 5, 2026
c2facbf
Adding a comment pointing out inconsistent representation of OuterRef…
Slimsammylim Feb 5, 2026
d835915
JSONs use 2 spaces not tabs. Reordering switch case in feield_referen…
Slimsammylim Feb 5, 2026
0ab1910
Update expr/lambda_test.go
benbellick Feb 6, 2026
e6069f9
Update expr/lambda_test.go
benbellick Feb 6, 2026
c5dd308
Update expr/field_reference.go
benbellick Feb 6, 2026
fbcbac6
Update expr/builder.go
benbellick Feb 6, 2026
1129979
Update expr/lambda_test.go
benbellick Feb 6, 2026
4e3b3b2
Update expr/lambda_test.go
benbellick Feb 6, 2026
478a2a8
Apply suggestion from @benbellick
benbellick Feb 6, 2026
bd58944
Update expr/lambda_test.go
Slimsammylim Feb 6, 2026
5968f91
Update expr/lambda_test.go
Slimsammylim Feb 6, 2026
8a8677f
Update expr/lambda_test.go
Slimsammylim Feb 6, 2026
6c4dcba
Update expr/lambda_test.go
Slimsammylim Feb 6, 2026
2a36a79
Modifying testcases to include uris and urns
Slimsammylim Feb 6, 2026
be9a260
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Feb 6, 2026
06ad1c7
Apply suggestions from code review
benbellick Feb 6, 2026
59c3cb3
Removing unecessary JSON tests
Slimsammylim Feb 6, 2026
ed2b93d
Merge branch 'slim/adding-lambda-support' of github.com:Slimsammylim/…
Slimsammylim Feb 6, 2026
e871e2d
New types test
Slimsammylim Feb 6, 2026
bfe987b
Using types in type resolution test
Slimsammylim Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 165 additions & 5 deletions expr/builder.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// SPDX-License-Identifier: Apache-2.0

// Package expr provides types and builders for constructing Substrait expressions.
//
// IMPORTANT: Only ExprBuilder methods guarantee construction of valid expressions. Manual
// construction using struct literals bypasses validation and may create invalid expressions. It is highly recommended to only construct expressions via the builders
package expr

import (
"fmt"

substraitgo "github.com/substrait-io/substrait-go/v7"
"github.com/substrait-io/substrait-go/v7/extensions"
"github.com/substrait-io/substrait-go/v7/types"
)
Expand All @@ -18,9 +25,9 @@ type Builder interface {

// ExprBuilder is the parent context for all the expression builders.
// It maintains a pointer to an extension registry and, optionally,
// a pointer to a base input schema. This allows less verbose expression
// building as it isn't necessary to pass these to every `New*` function
// to construct the expressions.
// a pointer to a base input schema.
// This allows less verbose expression building as it isn't necessary
// to pass these to every `New*` function to construct the expressions.
//
// This is intended to be used like:
//
Expand All @@ -36,8 +43,9 @@ type Builder interface {
//
// See the unit tests for additional examples / constructs.
type ExprBuilder struct {
Reg ExtensionRegistry
BaseSchema *types.RecordType
Reg ExtensionRegistry
BaseSchema *types.RecordType
lambdaContext []*types.StructType // keeps track of the lambda context for nested lambdas
}

// Literal returns a wrapped literal that can be passed as an argument
Expand Down Expand Up @@ -146,6 +154,21 @@ func (e *ExprBuilder) RootRef(ref Reference) *fieldRefBuilder {
return e.Ref(RootReference, ref)
}

// LambdaParamRef constructs a field reference to a lambda parameter.
//
// The stepsOut parameter specifies how many lambda scopes to traverse outward from
// the current lambda to find the target parameter.
//
// The Build method validates the stepsOut value and field index against the
// available lambda context, and resolves the reference type automatically.
func (e *ExprBuilder) LambdaParamRef(ref Reference, stepsOut uint32) *lambdaParamRefBuilder {
return &lambdaParamRefBuilder{
b: e,
ref: ref,
stepsOut: stepsOut,
}
}

// Cast returns a builder for constructing a Cast expression. The failure
// behavior can be specified by calling FailBehavior before calling Build.
func (e *ExprBuilder) Cast(from Builder, to types.Type) *castBuilder {
Expand All @@ -154,6 +177,83 @@ func (e *ExprBuilder) Cast(from Builder, to types.Type) *castBuilder {
}
}

// Lambda returns a builder for constructing a Lambda expression with the
// given parameters.
//
// When building nested lambdas (e.g., a function that takes a lambda argument
// which itself references outer lambda parameters), the ExprBuilder maintains
// a context stack that allows inner lambdas to validate stepsOut references
// against outer lambda parameters.
func (e *ExprBuilder) Lambda(params *types.StructType, body Builder) *lambdaBuilder {
return &lambdaBuilder{
b: e,
params: params,
body: body,
}
}

// pushLambdaContext pushes a lambda parameters struct onto the expression builder's context stack.
func (e *ExprBuilder) pushLambdaContext(params *types.StructType) {
e.lambdaContext = append(e.lambdaContext, params)
}

// popLambdaContext pops a lambda parameters struct off the expression builder's context stack.
func (e *ExprBuilder) popLambdaContext() {
e.lambdaContext = e.lambdaContext[:len(e.lambdaContext)-1]
}

type lambdaBuilder struct {
b *ExprBuilder
params *types.StructType
body Builder
}

// Build constructs and validates the Lambda expression.
func (lb *lambdaBuilder) Build() (*Lambda, error) {
if lb.params == nil {
return nil, fmt.Errorf("%w: lambda must have parameters struct", substraitgo.ErrInvalidExpr)
}
if lb.params.Nullability != types.NullabilityRequired {
return nil, fmt.Errorf("%w: lambda parameters struct must have NULLABILITY_REQUIRED", substraitgo.ErrInvalidExpr)
}
for i, paramType := range lb.params.Types {
if paramType == nil {
return nil, fmt.Errorf("%w: lambda parameter %d has nil type", substraitgo.ErrInvalidExpr, i)
}
}

if lb.body == nil {
return nil, fmt.Errorf("%w: lambda must have a body", substraitgo.ErrInvalidExpr)
}

// Push this lambda's params onto context stack before building body.
// This allows nested lambdas to validate stepsOut references against
// outer lambda parameters.
lb.b.pushLambdaContext(lb.params)

bodyExpr, err := lb.body.BuildExpr()

// Pop our params from context stack (always, even on error)
lb.b.popLambdaContext()

if err != nil {
return nil, err
}

return &Lambda{Parameters: lb.params, Body: bodyExpr}, nil
}

// BuildExpr implements the Builder interface.
func (lb *lambdaBuilder) BuildExpr() (Expression, error) {
return lb.Build()
}

// BuildFuncArg implements the FuncArgBuilder interface, allowing lambdas
// to be passed directly as arguments to function builders.
func (lb *lambdaBuilder) BuildFuncArg() (types.FuncArg, error) {
return lb.Build()
}

type exprWrapper struct {
expression Expression
err error
Expand Down Expand Up @@ -407,3 +507,63 @@ func (rb *fieldRefBuilder) BuildFuncArg() (types.FuncArg, error) {
func (rb *fieldRefBuilder) BuildExpr() (Expression, error) {
return rb.Build()
}

type lambdaParamRefBuilder struct {
b *ExprBuilder
ref Reference
stepsOut uint32
}

func (lpb *lambdaParamRefBuilder) Build() (*FieldReference, error) {
// Get the target lambda parameters
resolvedType, err := lpb.getLambdaParamType()
if err != nil {
return nil, err
}

return &FieldReference{
Root: LambdaParameterReference{StepsOut: lpb.stepsOut},
Reference: lpb.ref,
knownType: resolvedType,
}, nil
}

// getLambdaParamType gets the target lambda parameters struct from the context stack and validates that the parameter exists.
// Returns an error if the parameter does not exist.
func (lpb *lambdaParamRefBuilder) getLambdaParamType() (types.Type, error) {
// Lambda parameter references must always be StructFieldRef because lambda parameters are always a StructType
structRef, ok := lpb.ref.(*StructFieldRef)
if !ok {
return nil, fmt.Errorf("%w: lambda parameter reference must be StructFieldRef, got %T",
substraitgo.ErrInvalidExpr, lpb.ref)
}

// Validate stepsOut - check we have enough outer lambdas
if int(lpb.stepsOut) >= len(lpb.b.lambdaContext) {
return nil, fmt.Errorf("%w: stepsOut %d references non-existent outer lambda (only %d outer lambdas available)",
substraitgo.ErrInvalidExpr, lpb.stepsOut, len(lpb.b.lambdaContext))
}
targetParams := lpb.b.lambdaContext[len(lpb.b.lambdaContext)-1-int(lpb.stepsOut)]

// Validate that the parameter field index exists
if int(structRef.Field) >= len(targetParams.Types) {
return nil, fmt.Errorf("%w: lambda body references parameter %d but lambda only has %d parameters",
substraitgo.ErrInvalidExpr, structRef.Field, len(targetParams.Types))
}

// Resolve type using GetType method
resolvedType, err := structRef.GetType(targetParams)
if err != nil {
return nil, fmt.Errorf("%w: cannot resolve lambda parameter reference type: %w",
substraitgo.ErrInvalidExpr, err)
}
return resolvedType, nil
}

func (lpb *lambdaParamRefBuilder) BuildExpr() (Expression, error) {
return lpb.Build()
}

func (lpb *lambdaParamRefBuilder) BuildFuncArg() (types.FuncArg, error) {
return lpb.Build()
}
30 changes: 29 additions & 1 deletion expr/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,36 @@ func ExprFromProto(e *proto.Expression, baseSchema *types.RecordType, reg Extens
return nil, fmt.Errorf("%w: subquery expressions require a subquery converter to be configured", substraitgo.ErrNotImplemented)
}
return reg.SubqueryFromProto(et.Subquery, baseSchema, reg)
}
case *proto.Expression_Lambda_:
if et.Lambda.Parameters == nil {
return nil, fmt.Errorf("%w: lambda parameters cannot be nil", substraitgo.ErrInvalidExpr)
}
if et.Lambda.Body == nil {
return nil, fmt.Errorf("%w: lambda body cannot be nil", substraitgo.ErrInvalidExpr)
}

paramTypes := make([]types.Type, len(et.Lambda.Parameters.Types))
for i, pt := range et.Lambda.Parameters.Types {
paramTypes[i] = types.TypeFromProto(pt)
}
params := &types.StructType{
Types: paramTypes,
TypeVariationRef: et.Lambda.Parameters.TypeVariationReference,
Nullability: et.Lambda.Parameters.Nullability,
}

if params.Nullability != types.NullabilityRequired {
return nil, fmt.Errorf("%w: lambda parameters struct must have NULLABILITY_REQUIRED", substraitgo.ErrInvalidExpr)
}

body, err := ExprFromProto(et.Lambda.Body, baseSchema, reg)
if err != nil {
return nil, err
}

// TODO (#189): add validation and type resolution for lambda parameter references
return &Lambda{Parameters: params, Body: body}, nil
}
return nil, fmt.Errorf("%w: ExprFromProto: %s", substraitgo.ErrNotImplemented, e)
}

Expand Down
27 changes: 27 additions & 0 deletions expr/field_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ type OuterReference uint32

func (OuterReference) isRootRef() {}

// LambdaParameterReference is a root reference type for accessing lambda parameters
// within a lambda body expression. StepsOut indicates how many lambda boundaries
// to traverse up (0 = current lambda, 1 = outer lambda, etc.)
type LambdaParameterReference struct {
StepsOut uint32
}

func (LambdaParameterReference) isRootRef() {}

type ReferenceSegment interface {
Reference
fmt.Stringer
Expand Down Expand Up @@ -619,6 +628,8 @@ func (f *FieldReference) String() string {
b.WriteString(")]")
} else if outerRef, ok := f.Root.(OuterReference); ok {
fmt.Fprintf(&b, "[outerRef:%d]", outerRef)
} else if lambdaRef, ok := f.Root.(LambdaParameterReference); ok {
fmt.Fprintf(&b, "[lambdaParamRef:%d]", lambdaRef.StepsOut)
}

var typ string
Expand Down Expand Up @@ -657,6 +668,12 @@ func (f *FieldReference) ToProtoFieldRef() *proto.Expression_FieldReference {
StepsOut: uint32(r),
},
}
case LambdaParameterReference:
ret.RootType = &proto.Expression_FieldReference_LambdaParameterReference_{
LambdaParameterReference: &proto.Expression_FieldReference_LambdaParameterReference{
StepsOut: r.StepsOut,
},
}
}
} else {
ret.RootType = &proto.Expression_FieldReference_RootReference_{
Expand Down Expand Up @@ -696,6 +713,14 @@ func (f *FieldReference) Equals(rhs Expression) bool {
if !root.Equals(rhsExpr) {
return false
}
case LambdaParameterReference:
rhsRoot, ok := rhs.Root.(LambdaParameterReference)
if !ok {
return false
}
if rhsRoot.StepsOut != root.StepsOut {
return false
}
default:
if rhs.Root != RootReference {
return false
Expand Down Expand Up @@ -750,6 +775,8 @@ func FieldReferenceFromProto(p *proto.Expression_FieldReference, baseSchema *typ
root = OuterReference(rt.OuterReference.StepsOut)
case *proto.Expression_FieldReference_RootReference_:
root = RootReference
case *proto.Expression_FieldReference_LambdaParameterReference_:
root = LambdaParameterReference{StepsOut: rt.LambdaParameterReference.StepsOut}
}

switch rt := p.ReferenceType.(type) {
Expand Down
86 changes: 86 additions & 0 deletions expr/lambda.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package expr

import (
"fmt"
"strings"

"github.com/substrait-io/substrait-go/v7/types"
proto "github.com/substrait-io/substrait-protobuf/go/substraitpb"
)

// Lambda represents a lambda expression with parameters and a body.
type Lambda struct {
Parameters *types.StructType // The formal lambda parameters, required to have NULLABILITY_REQUIRED
Body Expression
}

func (l *Lambda) String() string {
var b strings.Builder
b.WriteString("(")
for i, t := range l.Parameters.Types {
if i > 0 {
b.WriteString(", ")
}
fmt.Fprintf(&b, "$%d: %s", i, t)
}
b.WriteString(") -> ")
b.WriteString(l.Body.String())
return b.String()
}

func (l *Lambda) isRootRef() {}

func (l *Lambda) IsScalar() bool {
return false
}

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

func (l *Lambda) Equals(other Expression) bool {
rhs, ok := other.(*Lambda)
if !ok {
return false
}
return l.Parameters.Equals(rhs.Parameters) && l.Body.Equals(rhs.Body)
}

func (l *Lambda) ToProto() *proto.Expression {
children := make([]*proto.Type, len(l.Parameters.Types))
for i, c := range l.Parameters.Types {
children[i] = types.TypeToProto(c)
}
paramsProto := &proto.Type_Struct{
Types: children,
TypeVariationReference: l.Parameters.TypeVariationRef,
Nullability: l.Parameters.Nullability,
}

return &proto.Expression{
RexType: &proto.Expression_Lambda_{
Lambda: &proto.Expression_Lambda{
Parameters: paramsProto,
Body: l.Body.ToProto(),
},
},
}
}

func (l *Lambda) ToProtoFuncArg() *proto.FunctionArgument {
return &proto.FunctionArgument{
ArgType: &proto.FunctionArgument_Value{Value: l.ToProto()},
}
}

func (l *Lambda) Visit(visit VisitFunc) Expression {
newBody := visit(l.Body)
if newBody == l.Body {
return l
}
return &Lambda{Parameters: l.Parameters, Body: newBody}
}
Loading