From c2aa77cb925f34f9177fdf073976553825c4a4f5 Mon Sep 17 00:00:00 2001 From: "M.V. Hutz" Date: Sat, 17 Jan 2026 20:46:07 +0000 Subject: [PATCH] refactor: remove visitor pattern (#37) ## Description The codebase previously used the visitor pattern for traversing lambda calculus expressions. This was a hold-over from avoiding the Go-idiomatic way of handling types. This PR removes the visitor pattern in favor of direct method implementations. - Remove `Visitor` interface from `expression.go`. - Remove `Accept` methods from `Abstraction`, `Application`, and `Variable`. - Remove `Accept` from `Expression` interface. - Delete `stringify.go` and move `String()` logic directly into each type. - Add compile-time interface checks (`var _ Expression = (*Type)(nil)`). - Update `expr.Expression` to embed `fmt.Stringer` instead of declaring `String() string`. ### Decisions - Moved `String()` implementations directly into each expression type rather than using a separate recursive function, as each type's string representation is simple enough to be self-contained. ## Benefits - Simpler, more idiomatic Go code using type methods instead of visitor pattern. - Reduced indirection and fewer files to maintain. - Compile-time interface satisfaction checks catch implementation errors early. ## Checklist - [x] Code follows conventional commit format. - [x] Branch follows naming convention (`/`). - [x] Tests pass (if applicable). - [ ] Documentation updated (if applicable). Closes #36 Reviewed-on: https://git.maximhutz.com/mvhutz/lambda/pulls/37 Co-authored-by: M.V. Hutz Co-committed-by: M.V. Hutz --- pkg/expr/expr.go | 8 ++++++-- pkg/lambda/expression.go | 43 ++++++++++++++-------------------------- pkg/lambda/stringify.go | 32 ------------------------------ 3 files changed, 21 insertions(+), 62 deletions(-) delete mode 100644 pkg/lambda/stringify.go diff --git a/pkg/expr/expr.go b/pkg/expr/expr.go index 4fd8b78..a041966 100644 --- a/pkg/expr/expr.go +++ b/pkg/expr/expr.go @@ -2,10 +2,14 @@ // expression types in the lambda interpreter. package expr +import ( + "fmt" +) + // Expression is the base interface for all evaluatable expression types. // Different evaluation modes (lambda calculus, SKI combinators, typed lambda // calculus, etc.) implement this interface with their own concrete types. type Expression interface { - // String returns a human-readable representation of the expression. - String() string + // The expression should have a human-readable representation. + fmt.Stringer } diff --git a/pkg/lambda/expression.go b/pkg/lambda/expression.go index 91e1502..7cb838a 100644 --- a/pkg/lambda/expression.go +++ b/pkg/lambda/expression.go @@ -1,12 +1,13 @@ package lambda -import "git.maximhutz.com/max/lambda/pkg/expr" +import ( + "git.maximhutz.com/max/lambda/pkg/expr" +) // Expression is the interface for all lambda calculus expression types. // It embeds the general expr.Expression interface for cross-mode compatibility. type Expression interface { expr.Expression - Accept(Visitor) } /** ------------------------------------------------------------------------- */ @@ -16,6 +17,8 @@ type Abstraction struct { body Expression } +var _ Expression = (*Abstraction)(nil) + func (a *Abstraction) Parameter() string { return a.parameter } @@ -24,16 +27,12 @@ func (a *Abstraction) Body() Expression { return a.body } -func (a *Abstraction) Accept(v Visitor) { - v.VisitAbstraction(a) -} - func (a *Abstraction) String() string { - return Stringify(a) + return "\\" + a.parameter + "." + a.body.String() } func NewAbstraction(parameter string, body Expression) *Abstraction { - return &Abstraction{parameter: parameter, body: body} + return &Abstraction{parameter, body} } /** ------------------------------------------------------------------------- */ @@ -43,6 +42,8 @@ type Application struct { argument Expression } +var _ Expression = (*Application)(nil) + func (a *Application) Abstraction() Expression { return a.abstraction } @@ -51,16 +52,12 @@ func (a *Application) Argument() Expression { return a.argument } -func (a *Application) Accept(v Visitor) { - v.VisitApplication(a) -} - func (a *Application) String() string { - return Stringify(a) + return "(" + a.abstraction.String() + " " + a.argument.String() + ")" } func NewApplication(abstraction Expression, argument Expression) *Application { - return &Application{abstraction: abstraction, argument: argument} + return &Application{abstraction, argument} } /** ------------------------------------------------------------------------- */ @@ -69,26 +66,16 @@ type Variable struct { value string } +var _ Expression = (*Variable)(nil) + func (v *Variable) Value() string { return v.value } -func (v *Variable) Accept(visitor Visitor) { - visitor.VisitVariable(v) -} - func (v *Variable) String() string { - return Stringify(v) + return v.value } func NewVariable(name string) *Variable { - return &Variable{value: name} -} - -/** ------------------------------------------------------------------------- */ - -type Visitor interface { - VisitAbstraction(*Abstraction) - VisitApplication(*Application) - VisitVariable(*Variable) + return &Variable{name} } diff --git a/pkg/lambda/stringify.go b/pkg/lambda/stringify.go deleted file mode 100644 index 1d838b2..0000000 --- a/pkg/lambda/stringify.go +++ /dev/null @@ -1,32 +0,0 @@ -package lambda - -import "strings" - -type stringifyVisitor struct { - builder strings.Builder -} - -func (v *stringifyVisitor) VisitVariable(a *Variable) { - v.builder.WriteString(a.value) -} - -func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) { - v.builder.WriteRune('\\') - v.builder.WriteString(f.parameter) - v.builder.WriteRune('.') - f.body.Accept(v) -} - -func (v *stringifyVisitor) VisitApplication(c *Application) { - v.builder.WriteRune('(') - c.abstraction.Accept(v) - v.builder.WriteRune(' ') - c.argument.Accept(v) - v.builder.WriteRune(')') -} - -func Stringify(e Expression) string { - b := &stringifyVisitor{builder: strings.Builder{}} - e.Accept(b) - return b.builder.String() -}