From a61809ad1d2ce70af55e7a7b03e7e60b5e48b724 Mon Sep 17 00:00:00 2001 From: "M.V. Hutz" Date: Fri, 16 Jan 2026 18:40:17 -0500 Subject: [PATCH] refactor: extract Reducer interface and update engine to use abstractions Introduce reducer.Reducer interface and update the engine to use abstract expr.Expression and reducer.Reducer types, enabling pluggable reduction strategies. - Add pkg/reducer/reducer.go with Reducer interface. - Add pkg/lambda/reducer.go with NormalOrderReducer implementation. - Update engine to accept expr.Expression and reducer.Reducer. - Update plugins to use expr.Expression directly (no pointer dereference). - Update main and tests to pass reducer to engine. Closes #30 --- cmd/lambda/lambda.go | 8 +++++--- cmd/lambda/lambda_test.go | 7 ++++--- internal/engine/engine.go | 18 +++++++++++------- internal/plugins/debug.go | 2 +- internal/plugins/explanation.go | 4 ++-- pkg/lambda/reducer.go | 33 +++++++++++++++++++++++++++++++++ pkg/reducer/reducer.go | 15 +++++++++++++++ 7 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 pkg/lambda/reducer.go create mode 100644 pkg/reducer/reducer.go diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index 050c727..2679d27 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -8,6 +8,7 @@ import ( "git.maximhutz.com/max/lambda/internal/engine" "git.maximhutz.com/max/lambda/internal/plugins" "git.maximhutz.com/max/lambda/pkg/convert" + "git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/saccharine" ) @@ -33,8 +34,9 @@ func main() { compiled := convert.SaccharineToLambda(ast) logger.Info("compiled λ expression", "tree", compiled.String()) - // Create reduction engine. - process := engine.New(options, &compiled) + // Create reduction engine with normal order reducer. + reducer := lambda.NewNormalOrderReducer() + process := engine.New(options, compiled, reducer) // If the user selected to track CPU performance, attach a profiler to the // process. @@ -61,7 +63,7 @@ func main() { process.Run() // Return the final reduced result. - result := (*process.Expression).String() + result := process.Expression.String() err = options.Destination.Write(result) cli.HandleError(err) } diff --git a/cmd/lambda/lambda_test.go b/cmd/lambda/lambda_test.go index 9f827f8..6c84fd6 100644 --- a/cmd/lambda/lambda_test.go +++ b/cmd/lambda/lambda_test.go @@ -41,11 +41,12 @@ func runSample(samplePath string) (string, error) { Verbose: false, } - // Create and run the engine. - process := engine.New(cfg, &compiled) + // Create and run the engine with normal order reducer. + reducer := lambda.NewNormalOrderReducer() + process := engine.New(cfg, compiled, reducer) process.Run() - return lambda.Stringify(compiled) + "\n", nil + return process.Expression.String() + "\n", nil } // Test that all samples produce expected output. diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 830eeac..ea075b4 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -5,30 +5,34 @@ package engine import ( "git.maximhutz.com/max/lambda/internal/config" "git.maximhutz.com/max/lambda/pkg/emitter" - "git.maximhutz.com/max/lambda/pkg/lambda" + "git.maximhutz.com/max/lambda/pkg/expr" + "git.maximhutz.com/max/lambda/pkg/reducer" ) -// A process for reducing one expression. +// Engine is a process for reducing one expression. type Engine struct { Config *config.Config - Expression *lambda.Expression + Expression expr.Expression + Reducer reducer.Reducer emitter.BaseEmitter[Event] } -// Create a new engine, given an unreduced λ-expression. -func New(config *config.Config, expression *lambda.Expression) *Engine { +// New creates a new engine with the given expression and reducer. +func New(config *config.Config, expression expr.Expression, reducer reducer.Reducer) *Engine { return &Engine{ Config: config, Expression: expression, + Reducer: reducer, BaseEmitter: *emitter.New[Event](), } } -// Begin the reduction process. +// Run begins the reduction process. func (e *Engine) Run() { e.Emit(StartEvent) - lambda.ReduceAll(e.Expression, func() { + e.Expression = e.Reducer.Reduce(e.Expression, func(reduced expr.Expression) { + e.Expression = reduced e.Emit(StepEvent) }) diff --git a/internal/plugins/debug.go b/internal/plugins/debug.go index 8f0ec8e..1c1a95b 100644 --- a/internal/plugins/debug.go +++ b/internal/plugins/debug.go @@ -19,5 +19,5 @@ func NewLogs(logger *slog.Logger, process *engine.Engine) *Logs { } func (t *Logs) Step() { - t.logger.Info("reduction", "tree", (*t.process.Expression).String()) + t.logger.Info("reduction", "tree", t.process.Expression.String()) } diff --git a/internal/plugins/explanation.go b/internal/plugins/explanation.go index 4fcadce..ef46bb9 100644 --- a/internal/plugins/explanation.go +++ b/internal/plugins/explanation.go @@ -23,9 +23,9 @@ func NewExplanation(process *engine.Engine) *Explanation { } func (t *Explanation) Start() { - fmt.Println((*t.process.Expression).String()) + fmt.Println(t.process.Expression.String()) } func (t *Explanation) Step() { - fmt.Println(" =", (*t.process.Expression).String()) + fmt.Println(" =", t.process.Expression.String()) } diff --git a/pkg/lambda/reducer.go b/pkg/lambda/reducer.go new file mode 100644 index 0000000..b806c62 --- /dev/null +++ b/pkg/lambda/reducer.go @@ -0,0 +1,33 @@ +package lambda + +import ( + "git.maximhutz.com/max/lambda/pkg/expr" + "git.maximhutz.com/max/lambda/pkg/reducer" +) + +// Ensure NormalOrderReducer implements reducer.Reducer. +var _ reducer.Reducer = (*NormalOrderReducer)(nil) + +// NormalOrderReducer implements normal order (leftmost-outermost) reduction +// for lambda calculus expressions. +type NormalOrderReducer struct{} + +// NewNormalOrderReducer creates a new normal order reducer. +func NewNormalOrderReducer() *NormalOrderReducer { + return &NormalOrderReducer{} +} + +// Reduce performs normal order reduction on a lambda expression. +// The expression must be a lambda.Expression; other types are returned unchanged. +func (r *NormalOrderReducer) Reduce(e expr.Expression, onStep func(expr.Expression)) expr.Expression { + lambdaExpr, ok := e.(Expression) + if !ok { + return e + } + + ReduceAll(&lambdaExpr, func() { + onStep(lambdaExpr) + }) + + return lambdaExpr +} diff --git a/pkg/reducer/reducer.go b/pkg/reducer/reducer.go new file mode 100644 index 0000000..75d2dd9 --- /dev/null +++ b/pkg/reducer/reducer.go @@ -0,0 +1,15 @@ +// Package reducer provides the abstract Reducer interface for all expression +// reduction strategies. +package reducer + +import "git.maximhutz.com/max/lambda/pkg/expr" + +// Reducer defines the interface for expression reduction strategies. +// Different evaluation modes (normal order, applicative order, SKI combinators, +// etc.) implement this interface with their own reduction logic. +type Reducer interface { + // Reduce performs all reduction steps on the expression, calling onStep + // after each reduction. + // Returns the final reduced expression. + Reduce(e expr.Expression, onStep func(expr.Expression)) expr.Expression +} -- 2.49.1