refactor: move event system to reducer, remove engine package (#32)

## Description

This PR completes the MVC-inspired refactoring by moving the event system from the engine into the reducer.
The engine package is now removed entirely, as the reducer handles both reduction logic and lifecycle events.

- Add `pkg/reducer/events.go` with `StartEvent`, `StepEvent`, and `StopEvent`.
- Extend `Reducer` interface to embed `Emitter[Event]` and add `Expression()` method.
- Update `NormalOrderReducer` to embed `BaseEmitter` and emit lifecycle events during reduction.
- Update all plugins to attach to `Reducer` instead of `Engine`.
- Remove `internal/engine` package entirely.
- Add `Off()` method to `BaseEmitter` to complete the `Emitter` interface.
- Fix `Emitter.On` signature to use generic type `E` instead of `string`.

### Decisions

- The `Reducer` interface now combines reduction logic with event emission, making it the single orchestration point.
- Plugins attach directly to the reducer, simplifying the architecture.
- The `Expression()` method on `Reducer` provides access to current state for plugins.

## Benefits

- Simpler architecture with one fewer abstraction layer.
- Plugins are now mode-agnostic - they work with any `Reducer` implementation.
- Cleaner separation: reducers handle reduction, plugins observe via events.
- Easier to add new evaluation modes - just implement `Reducer` with embedded emitter.

## Checklist

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

Reviewed-on: #32
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 #32.
This commit is contained in:
2026-01-17 00:27:36 +00:00
committed by Maxim Hutz
parent f8e1223463
commit 1974ad582f
14 changed files with 143 additions and 154 deletions

View File

@@ -1,40 +0,0 @@
// Package "engine" provides an extensible interface for users to interact with
// λ-calculus and other expression evaluation modes.
package engine
import (
"git.maximhutz.com/max/lambda/internal/config"
"git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Engine is a process for reducing one expression.
type Engine struct {
Config *config.Config
Expression expr.Expression
Reducer reducer.Reducer
emitter.BaseEmitter[Event]
}
// 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](),
}
}
// Run begins the reduction process.
func (e *Engine) Run() {
e.Emit(StartEvent)
e.Expression = e.Reducer.Reduce(e.Expression, func(reduced expr.Expression) {
e.Expression = reduced
e.Emit(StepEvent)
})
e.Emit(StopEvent)
}

View File

@@ -1,9 +0,0 @@
package engine
type Event int
const (
StartEvent Event = iota
StepEvent
StopEvent
)

View File

@@ -3,21 +3,21 @@ package plugins
import (
"log/slog"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
type Logs struct {
logger *slog.Logger
process *engine.Engine
reducer reducer.Reducer
}
func NewLogs(logger *slog.Logger, process *engine.Engine) *Logs {
plugin := &Logs{logger, process}
process.On(engine.StepEvent, plugin.Step)
func NewLogs(logger *slog.Logger, r reducer.Reducer) *Logs {
plugin := &Logs{logger, r}
r.On(reducer.StepEvent, plugin.Step)
return plugin
}
func (t *Logs) Step() {
t.logger.Info("reduction", "tree", t.process.Expression.String())
t.logger.Info("reduction", "tree", t.reducer.Expression().String())
}

View File

@@ -5,27 +5,27 @@ package plugins
import (
"fmt"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Track the reductions made by a reduction process.
type Explanation struct {
process *engine.Engine
reducer reducer.Reducer
}
// Attaches a new explanation tracker to a process.
func NewExplanation(process *engine.Engine) *Explanation {
plugin := &Explanation{process: process}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StepEvent, plugin.Step)
// Attaches a new explanation tracker to a reducer.
func NewExplanation(r reducer.Reducer) *Explanation {
plugin := &Explanation{reducer: r}
r.On(reducer.StartEvent, plugin.Start)
r.On(reducer.StepEvent, plugin.Step)
return plugin
}
func (t *Explanation) Start() {
fmt.Println(t.process.Expression.String())
fmt.Println(t.reducer.Expression().String())
}
func (t *Explanation) Step() {
fmt.Println(" =", t.process.Expression.String())
fmt.Println(" =", t.reducer.Expression().String())
}

View File

@@ -7,7 +7,7 @@ import (
"path/filepath"
"runtime/pprof"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Observes a reduction process, and publishes a CPU performance profile on
@@ -19,10 +19,10 @@ type Performance struct {
}
// Create a performance tracker that outputs a profile to "file".
func NewPerformance(file string, process *engine.Engine) *Performance {
func NewPerformance(file string, process reducer.Reducer) *Performance {
plugin := &Performance{File: file}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StopEvent, plugin.Stop)
process.On(reducer.StartEvent, plugin.Start)
process.On(reducer.StopEvent, plugin.Stop)
return plugin
}

View File

@@ -5,8 +5,8 @@ import (
"os"
"time"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/internal/statistics"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// An observer, to track reduction performance.
@@ -16,11 +16,11 @@ type Statistics struct {
}
// Create a new reduction performance Statistics.
func NewStatistics(process *engine.Engine) *Statistics {
func NewStatistics(r reducer.Reducer) *Statistics {
plugin := &Statistics{}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StepEvent, plugin.Step)
process.On(engine.StopEvent, plugin.Stop)
r.On(reducer.StartEvent, plugin.Start)
r.On(reducer.StepEvent, plugin.Step)
r.On(reducer.StopEvent, plugin.Stop)
return plugin
}