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 (`<type>/<description>`).
- [x] Tests pass (if applicable).
- [ ] Documentation updated (if applicable).

Closes #36

Reviewed-on: #37
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
This commit was merged in pull request #37.
This commit is contained in:
2026-01-17 20:46:07 +00:00
committed by Maxim Hutz
parent 52d40adcc6
commit c2aa77cb92
3 changed files with 21 additions and 62 deletions

View File

@@ -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
}

View File

@@ -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}
}

View File

@@ -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()
}