refactor: extract Reducer interface and update engine to use abstractions (#31)

## Description

This PR builds on #30 to complete the abstraction layer for multi-mode evaluation support.
The engine now accepts abstract `expr.Expression` and `reducer.Reducer` types instead of concrete lambda types.

- Add `pkg/reducer/reducer.go` with `Reducer` interface defining `Reduce(expr.Expression, onStep) expr.Expression`.
- Add `pkg/lambda/reducer.go` with `NormalOrderReducer` that wraps the existing `ReduceAll` logic.
- Update `engine.Engine` to store `expr.Expression` and `reducer.Reducer` instead of `*lambda.Expression`.
- Update plugins to use `expr.Expression.String()` directly (no pointer dereference needed).
- Update main and tests to instantiate `NormalOrderReducer` and pass it to the engine.

### Decisions

- The `Reducer.Reduce` method returns the final expression and calls `onStep` after each reduction step with the current state.
- `NormalOrderReducer` type-asserts to `lambda.Expression` internally; other expression types are returned unchanged.
- The engine updates its `Expression` field both during reduction (via `onStep`) and after completion.

## Benefits

- The engine is now fully decoupled from lambda-specific types.
- New evaluation modes can be added by implementing `expr.Expression` and `reducer.Reducer`.
- Plugins work with any expression type that implements `expr.Expression`.
- Prepares the codebase for SKI combinators, typed lambda calculus, or other future modes.

## Checklist

- [x] Code follows conventional commit format.
- [x] Branch follows naming convention (`<type>/<description>`).
- [x] Tests pass (if applicable).
- [ ] Documentation updated (if applicable).

Closes #30

Reviewed-on: #31
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 #31.
This commit is contained in:
2026-01-16 23:42:07 +00:00
committed by Maxim Hutz
parent e0114c736d
commit f8e1223463
7 changed files with 71 additions and 16 deletions

View File

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

View File

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

View File

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