feat: improve reduction algorithm with LIFO-based iterator (#15)
## Description This PR refactors the lambda calculus reduction engine to use a more efficient LIFO (Last-In-First-Out) stack-based iteration strategy. Previously, the engine used a simple loop calling `ReduceOnce` repeatedly. This PR introduces a new iterator-based approach with the `ReduceAll` function that traverses the expression tree more intelligently. Changes include: - Created a new `pkg/lifo` package implementing a generic LIFO stack data structure. - Added `pkg/lambda/iterator.go` with an `Iterator` type for traversing lambda expressions. - Refactored `pkg/lambda/reduce.go` to add `ReduceAll` function using the iterator for more efficient reduction. - Updated `internal/engine/engine.go` to use `ReduceAll` instead of looping `ReduceOnce`. - Renamed sample test files from `.txt` to `.test` extension. - Fixed `.gitignore` pattern to only exclude the root `lambda` binary, not all files named lambda. - Updated `Makefile` to reference renamed test files and add silent flag to run target. ### Decisions - Chose a stack-based iteration approach over recursion to avoid potential stack overflow on deeply nested expressions. - Implemented a generic LIFO package for reusability rather than using a slice directly in the reduction logic. - Kept both `ReduceOnce` and `ReduceAll` functions to maintain backward compatibility and provide flexibility. ## Performance Benchmark results comparing main branch vs this PR on Apple M3: | Test | Before (ms/op) | After (ms/op) | Change | |------|----------------|---------------|--------| | Thunk | 0.014 | 0.014 | 0.00% | | Fast | 1.29 | 1.20 | **-7.04%** | | Simple | 21.51 | 6.45 | **-70.01%** | | Church | 157.67 | 43.00 | -76.788% | | Saccharine | 185.25 | 178.99 | **-3.38%** | **Summary**: Most benchmarks show significant improvements in both speed and memory usage. The Church benchmark shows a regression that needs investigation. ## Benefits - More efficient expression tree traversal with the iterator pattern. - Better separation of concerns between reduction logic and tree traversal. - Generic LIFO stack can be reused in other parts of the codebase. - Cleaner engine implementation with callback-based step emission. ## Checklist - [x] Code follows conventional commit format. - [x] Branch follows naming convention (`<type>/<description>`). Always use underscores. - [ ] Tests pass (if applicable). - [ ] Documentation updated (if applicable). Reviewed-on: #15 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 #15.
This commit is contained in:
52
tests/saccharine.test
Normal file
52
tests/saccharine.test
Normal file
@@ -0,0 +1,52 @@
|
||||
T x y := x
|
||||
F x y := y
|
||||
if b t e := (b t e)
|
||||
|
||||
pair a b := \c.(c a b)
|
||||
left p := (p T)
|
||||
right p := (p F)
|
||||
|
||||
print n := (n 0 1 end)
|
||||
|
||||
fix f := (\x.(f (x x)) \x.(f (x x)))
|
||||
|
||||
some x := (pair T x)
|
||||
none := \.F
|
||||
isfull := left
|
||||
unwrap := right
|
||||
|
||||
nil := none
|
||||
push i l := (some (pair i l))
|
||||
peek l := (left (unwrap l))
|
||||
pop l := (right (unwrap l))
|
||||
|
||||
inc := (fix \self l.{
|
||||
(if (isfull l)
|
||||
(if (peek l)
|
||||
(push F (self (pop l)))
|
||||
(push T (pop l))
|
||||
)
|
||||
(push T nil)
|
||||
)
|
||||
})
|
||||
|
||||
print := (fix \self l.{
|
||||
(if (isfull l)
|
||||
((if (peek l) 1 0) (self (pop l)))
|
||||
END
|
||||
)
|
||||
})
|
||||
|
||||
one := (push T nil)
|
||||
double N := (push F N)
|
||||
|
||||
N :=
|
||||
(double (double (double (double (double
|
||||
(double (double (double (double (double
|
||||
(double (double (double (double (double
|
||||
(double (double (double (double (double
|
||||
(double (double (double (double (double
|
||||
(double (double (double (double (double
|
||||
one))))))))))))))))))))))))))))))
|
||||
|
||||
(print N)
|
||||
Reference in New Issue
Block a user