refactor: make lambda expression types immutable (#38)
## Summary - Change Abstraction, Application, and Variable to use private fields with getter methods. - Return value types instead of pointers from constructors. - Update all type switches to match value types instead of pointer types. ## Test plan - [x] All existing tests pass (`make test`). Reviewed-on: #38 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 #38.
This commit is contained in:
@@ -34,34 +34,34 @@ func main() {
|
|||||||
logger.Info("compiled λ expression", "tree", compiled.String())
|
logger.Info("compiled λ expression", "tree", compiled.String())
|
||||||
|
|
||||||
// Create reducer with the compiled expression.
|
// Create reducer with the compiled expression.
|
||||||
reducer := lambda.NewNormalOrderReducer(&compiled)
|
interpreter := lambda.NewInterpreter(compiled)
|
||||||
|
|
||||||
// If the user selected to track CPU performance, attach a profiler.
|
// If the user selected to track CPU performance, attach a profiler.
|
||||||
if options.Profile != "" {
|
if options.Profile != "" {
|
||||||
plugins.NewPerformance(options.Profile, reducer)
|
plugins.NewPerformance(options.Profile, interpreter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user selected to produce a step-by-step explanation, attach an
|
// If the user selected to produce a step-by-step explanation, attach an
|
||||||
// observer.
|
// observer.
|
||||||
if options.Explanation {
|
if options.Explanation {
|
||||||
plugins.NewExplanation(reducer)
|
plugins.NewExplanation(interpreter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user opted to track statistics, attach a tracker.
|
// If the user opted to track statistics, attach a tracker.
|
||||||
if options.Statistics {
|
if options.Statistics {
|
||||||
plugins.NewStatistics(reducer)
|
plugins.NewStatistics(interpreter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user selected for verbose debug logs, attach a reduction tracker.
|
// If the user selected for verbose debug logs, attach a reduction tracker.
|
||||||
if options.Verbose {
|
if options.Verbose {
|
||||||
plugins.NewLogs(logger, reducer)
|
plugins.NewLogs(logger, interpreter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run reduction.
|
// Run reduction.
|
||||||
reducer.Reduce()
|
interpreter.Run()
|
||||||
|
|
||||||
// Return the final reduced result.
|
// Return the final reduced result.
|
||||||
result := reducer.Expression().String()
|
result := interpreter.Expression().String()
|
||||||
err = options.Destination.Write(result)
|
err = options.Destination.Write(result)
|
||||||
cli.HandleError(err)
|
cli.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ func runSample(samplePath string) (string, error) {
|
|||||||
compiled := convert.SaccharineToLambda(ast)
|
compiled := convert.SaccharineToLambda(ast)
|
||||||
|
|
||||||
// Create and run the reducer.
|
// Create and run the reducer.
|
||||||
reducer := lambda.NewNormalOrderReducer(&compiled)
|
reducer := lambda.NewInterpreter(compiled)
|
||||||
reducer.Reduce()
|
reducer.Run()
|
||||||
|
|
||||||
return reducer.Expression().String() + "\n", nil
|
return reducer.Expression().String() + "\n", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/interpreter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logs struct {
|
type Logs struct {
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
reducer reducer.Reducer
|
reducer interpreter.Interpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogs(logger *slog.Logger, r reducer.Reducer) *Logs {
|
func NewLogs(logger *slog.Logger, r interpreter.Interpreter) *Logs {
|
||||||
plugin := &Logs{logger, r}
|
plugin := &Logs{logger, r}
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(interpreter.StepEvent, plugin.Step)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/interpreter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Track the reductions made by a reduction process.
|
// Track the reductions made by a reduction process.
|
||||||
type Explanation struct {
|
type Explanation struct {
|
||||||
reducer reducer.Reducer
|
reducer interpreter.Interpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches a new explanation tracker to a reducer.
|
// Attaches a new explanation tracker to a reducer.
|
||||||
func NewExplanation(r reducer.Reducer) *Explanation {
|
func NewExplanation(r interpreter.Interpreter) *Explanation {
|
||||||
plugin := &Explanation{reducer: r}
|
plugin := &Explanation{reducer: r}
|
||||||
r.On(reducer.StartEvent, plugin.Start)
|
r.On(interpreter.StartEvent, plugin.Start)
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(interpreter.StepEvent, plugin.Step)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/interpreter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Observes a reduction process, and publishes a CPU performance profile on
|
// 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".
|
// Create a performance tracker that outputs a profile to "file".
|
||||||
func NewPerformance(file string, process reducer.Reducer) *Performance {
|
func NewPerformance(file string, process interpreter.Interpreter) *Performance {
|
||||||
plugin := &Performance{File: file}
|
plugin := &Performance{File: file}
|
||||||
process.On(reducer.StartEvent, plugin.Start)
|
process.On(interpreter.StartEvent, plugin.Start)
|
||||||
process.On(reducer.StopEvent, plugin.Stop)
|
process.On(interpreter.StopEvent, plugin.Stop)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/internal/statistics"
|
"git.maximhutz.com/max/lambda/internal/statistics"
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/interpreter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An observer, to track reduction performance.
|
// An observer, to track reduction performance.
|
||||||
@@ -16,11 +16,11 @@ type Statistics struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new reduction performance Statistics.
|
// Create a new reduction performance Statistics.
|
||||||
func NewStatistics(r reducer.Reducer) *Statistics {
|
func NewStatistics(r interpreter.Interpreter) *Statistics {
|
||||||
plugin := &Statistics{}
|
plugin := &Statistics{}
|
||||||
r.On(reducer.StartEvent, plugin.Start)
|
r.On(interpreter.StartEvent, plugin.Start)
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(interpreter.StepEvent, plugin.Step)
|
||||||
r.On(reducer.StopEvent, plugin.Stop)
|
r.On(interpreter.StopEvent, plugin.Stop)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
13
pkg/interpreter/events.go
Normal file
13
pkg/interpreter/events.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package interpreter
|
||||||
|
|
||||||
|
// Event represents lifecycle events during interpretation.
|
||||||
|
type Event int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StartEvent is emitted before interpretation begins.
|
||||||
|
StartEvent Event = iota
|
||||||
|
// StepEvent is emitted after each interpretation step.
|
||||||
|
StepEvent
|
||||||
|
// StopEvent is emitted after interpretation completes.
|
||||||
|
StopEvent
|
||||||
|
)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package reducer provides the abstract Reducer interface for all expression
|
// Package interpreter provides the abstract Reducer interface for all expression
|
||||||
// reduction strategies.
|
// reduction strategies.
|
||||||
package reducer
|
package interpreter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
"git.maximhutz.com/max/lambda/pkg/emitter"
|
||||||
@@ -13,14 +13,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Reducers also implement the Emitter interface to allow plugins to observe
|
// Reducers also implement the Emitter interface to allow plugins to observe
|
||||||
// reduction lifecycle events (Start, Step, Stop).
|
// reduction lifecycle events (Start, Step, Stop).
|
||||||
type Reducer interface {
|
type Interpreter interface {
|
||||||
emitter.Emitter[Event]
|
emitter.Emitter[Event]
|
||||||
|
|
||||||
// Reduce performs all reduction steps on the expression.
|
// Run a single step. Returns whether the interpreter is complete or not.
|
||||||
// Emits StartEvent before reduction, StepEvent after each step, and
|
Step() bool
|
||||||
// StopEvent after completion.
|
|
||||||
// Returns the final reduced expression.
|
// Run until completion.
|
||||||
Reduce()
|
Run()
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
// Expression returns the current expression state.
|
||||||
Expression() expr.Expression
|
Expression() expr.Expression
|
||||||
@@ -17,22 +17,22 @@ type Abstraction struct {
|
|||||||
body Expression
|
body Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Expression = (*Abstraction)(nil)
|
var _ Expression = Abstraction{}
|
||||||
|
|
||||||
func (a *Abstraction) Parameter() string {
|
func (a Abstraction) Parameter() string {
|
||||||
return a.parameter
|
return a.parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) Body() Expression {
|
func (a Abstraction) Body() Expression {
|
||||||
return a.body
|
return a.body
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) String() string {
|
func (a Abstraction) String() string {
|
||||||
return "\\" + a.parameter + "." + a.body.String()
|
return "\\" + a.parameter + "." + a.body.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAbstraction(parameter string, body Expression) *Abstraction {
|
func NewAbstraction(parameter string, body Expression) Abstraction {
|
||||||
return &Abstraction{parameter, body}
|
return Abstraction{parameter, body}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
@@ -42,40 +42,40 @@ type Application struct {
|
|||||||
argument Expression
|
argument Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Expression = (*Application)(nil)
|
var _ Expression = Application{}
|
||||||
|
|
||||||
func (a *Application) Abstraction() Expression {
|
func (a Application) Abstraction() Expression {
|
||||||
return a.abstraction
|
return a.abstraction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) Argument() Expression {
|
func (a Application) Argument() Expression {
|
||||||
return a.argument
|
return a.argument
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) String() string {
|
func (a Application) String() string {
|
||||||
return "(" + a.abstraction.String() + " " + a.argument.String() + ")"
|
return "(" + a.abstraction.String() + " " + a.argument.String() + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplication(abstraction Expression, argument Expression) *Application {
|
func NewApplication(abstraction Expression, argument Expression) Application {
|
||||||
return &Application{abstraction, argument}
|
return Application{abstraction, argument}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
value string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Expression = (*Variable)(nil)
|
var _ Expression = Variable{}
|
||||||
|
|
||||||
func (v *Variable) Value() string {
|
func (v Variable) Name() string {
|
||||||
return v.value
|
return v.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) String() string {
|
func (v Variable) String() string {
|
||||||
return v.value
|
return v.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVariable(name string) *Variable {
|
func NewVariable(name string) Variable {
|
||||||
return &Variable{name}
|
return Variable{name}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import (
|
|||||||
"git.maximhutz.com/max/lambda/pkg/set"
|
"git.maximhutz.com/max/lambda/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ticker uint64 = 0
|
||||||
|
|
||||||
|
// GenerateFreshName generates a variable name that is not in the used set.
|
||||||
|
// This function does not mutate the used set.
|
||||||
func GenerateFreshName(used *set.Set[string]) string {
|
func GenerateFreshName(used *set.Set[string]) string {
|
||||||
for i := uint64(0); ; i++ {
|
for i := uint64(0); ; i++ {
|
||||||
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
|
attempt := "_" + string(strconv.AppendUint(nil, ticker, 10))
|
||||||
|
|
||||||
if !used.Has(attempt) {
|
if !used.Has(attempt) {
|
||||||
return attempt
|
return attempt
|
||||||
|
|||||||
@@ -2,19 +2,22 @@ package lambda
|
|||||||
|
|
||||||
import "git.maximhutz.com/max/lambda/pkg/set"
|
import "git.maximhutz.com/max/lambda/pkg/set"
|
||||||
|
|
||||||
|
// GetFreeVariables returns the set of all free variable names in the expression.
|
||||||
|
// This function does not mutate the input expression.
|
||||||
|
// The returned set is newly allocated and can be modified by the caller.
|
||||||
func GetFreeVariables(e Expression) *set.Set[string] {
|
func GetFreeVariables(e Expression) *set.Set[string] {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case *Variable:
|
case Variable:
|
||||||
return set.New(e.value)
|
return set.New(e.Name())
|
||||||
case *Abstraction:
|
case Abstraction:
|
||||||
vars := GetFreeVariables(e.body)
|
vars := GetFreeVariables(e.Body())
|
||||||
vars.Remove(e.parameter)
|
vars.Remove(e.Parameter())
|
||||||
return vars
|
return vars
|
||||||
case *Application:
|
case Application:
|
||||||
vars := GetFreeVariables(e.abstraction)
|
vars := GetFreeVariables(e.Abstraction())
|
||||||
vars.Merge(GetFreeVariables(e.argument))
|
vars.Merge(GetFreeVariables(e.Argument()))
|
||||||
return vars
|
return vars
|
||||||
default:
|
default:
|
||||||
return nil
|
return set.New[string]()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
pkg/lambda/interpreter.go
Normal file
45
pkg/lambda/interpreter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package lambda
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/emitter"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/expr"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/interpreter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
||||||
|
// for lambda calculus expressions.
|
||||||
|
type Interpreter struct {
|
||||||
|
emitter.BaseEmitter[interpreter.Event]
|
||||||
|
expression Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNormalOrderReducer creates a new normal order reducer.
|
||||||
|
func NewInterpreter(expression Expression) *Interpreter {
|
||||||
|
return &Interpreter{
|
||||||
|
BaseEmitter: *emitter.New[interpreter.Event](),
|
||||||
|
expression: expression,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expression returns the current expression state.
|
||||||
|
func (r *Interpreter) Expression() expr.Expression {
|
||||||
|
return r.expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Interpreter) Step() bool {
|
||||||
|
result, done := ReduceOnce(r.expression)
|
||||||
|
r.expression = result
|
||||||
|
return !done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce performs normal order reduction on a lambda expression.
|
||||||
|
// The expression must be a lambda.Expression; other types are returned unchanged.
|
||||||
|
func (r *Interpreter) Run() {
|
||||||
|
r.Emit(interpreter.StartEvent)
|
||||||
|
|
||||||
|
for !r.Step() {
|
||||||
|
r.Emit(interpreter.StepEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Emit(interpreter.StopEvent)
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
|
// IsFreeVariable returns true if the variable name n occurs free in the expression.
|
||||||
|
// This function does not mutate the input expression.
|
||||||
func IsFreeVariable(n string, e Expression) bool {
|
func IsFreeVariable(n string, e Expression) bool {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case *Variable:
|
case Variable:
|
||||||
return e.value == n
|
return e.Name() == n
|
||||||
case *Abstraction:
|
case Abstraction:
|
||||||
return e.parameter != n && IsFreeVariable(n, e.body)
|
return e.Parameter() != n && IsFreeVariable(n, e.Body())
|
||||||
case *Application:
|
case Application:
|
||||||
return IsFreeVariable(n, e.abstraction) || IsFreeVariable(n, e.argument)
|
return IsFreeVariable(n, e.Abstraction()) || IsFreeVariable(n, e.Argument())
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package lambda
|
|
||||||
|
|
||||||
type Iterator struct {
|
|
||||||
trace []*Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIterator(expr *Expression) *Iterator {
|
|
||||||
return &Iterator{[]*Expression{expr}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Done() bool {
|
|
||||||
return len(i.trace) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Current() *Expression {
|
|
||||||
if i.Done() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Parent() *Expression {
|
|
||||||
if len(i.trace) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Swap(with Expression) {
|
|
||||||
current := i.Current()
|
|
||||||
if current != nil {
|
|
||||||
*current = with
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Back() bool {
|
|
||||||
if i.Done() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = i.trace[:len(i.trace)-1]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Next() {
|
|
||||||
switch typed := (*i.Current()).(type) {
|
|
||||||
case *Abstraction:
|
|
||||||
i.trace = append(i.trace, &typed.body)
|
|
||||||
case *Application:
|
|
||||||
i.trace = append(i.trace, &typed.abstraction)
|
|
||||||
case *Variable:
|
|
||||||
for len(i.trace) > 1 {
|
|
||||||
if app, ok := (*i.Parent()).(*Application); ok {
|
|
||||||
if app.abstraction == *i.Current() {
|
|
||||||
i.Back()
|
|
||||||
i.trace = append(i.trace, &app.argument)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.Back()
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = []*Expression{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
32
pkg/lambda/reduce.go
Normal file
32
pkg/lambda/reduce.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package lambda
|
||||||
|
|
||||||
|
func ReduceOnce(e Expression) (Expression, bool) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case Abstraction:
|
||||||
|
body, reduced := ReduceOnce(e.Body())
|
||||||
|
if reduced {
|
||||||
|
return NewAbstraction(e.Parameter(), body), true
|
||||||
|
}
|
||||||
|
return e, false
|
||||||
|
|
||||||
|
case Application:
|
||||||
|
if fn, fnOk := e.Abstraction().(Abstraction); fnOk {
|
||||||
|
return Substitute(fn.Body(), fn.Parameter(), e.Argument()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
abs, reduced := ReduceOnce(e.Abstraction())
|
||||||
|
if reduced {
|
||||||
|
return NewApplication(abs, e.Argument()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
arg, reduced := ReduceOnce(e.Argument())
|
||||||
|
if reduced {
|
||||||
|
return NewApplication(e.Abstraction(), arg), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, false
|
||||||
|
|
||||||
|
default:
|
||||||
|
return e, false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package lambda
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
|
||||||
// for lambda calculus expressions.
|
|
||||||
type NormalOrderReducer struct {
|
|
||||||
emitter.BaseEmitter[reducer.Event]
|
|
||||||
expression *Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNormalOrderReducer creates a new normal order reducer.
|
|
||||||
func NewNormalOrderReducer(expression *Expression) *NormalOrderReducer {
|
|
||||||
return &NormalOrderReducer{
|
|
||||||
BaseEmitter: *emitter.New[reducer.Event](),
|
|
||||||
expression: expression,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
|
||||||
func (r *NormalOrderReducer) Expression() expr.Expression {
|
|
||||||
return *r.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func isViable(e *Expression) (*Abstraction, Expression, bool) {
|
|
||||||
if e == nil {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if app, appOk := (*e).(*Application); !appOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if fn, fnOk := app.abstraction.(*Abstraction); !fnOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else {
|
|
||||||
return fn, app.argument, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
r.Emit(reducer.StartEvent)
|
|
||||||
it := NewIterator(r.expression)
|
|
||||||
|
|
||||||
for !it.Done() {
|
|
||||||
if fn, arg, ok := isViable(it.Current()); !ok {
|
|
||||||
it.Next()
|
|
||||||
} else {
|
|
||||||
it.Swap(Substitute(fn.body, fn.parameter, arg))
|
|
||||||
r.Emit(reducer.StepEvent)
|
|
||||||
|
|
||||||
if _, _, ok := isViable(it.Parent()); ok {
|
|
||||||
it.Back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Emit(reducer.StopEvent)
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,27 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
|
// Rename replaces all occurrences of the target variable name with the new name.
|
||||||
func Rename(expr Expression, target string, newName string) Expression {
|
func Rename(expr Expression, target string, newName string) Expression {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case *Variable:
|
case Variable:
|
||||||
if e.value == target {
|
if e.Name() == target {
|
||||||
return NewVariable(newName)
|
return NewVariable(newName)
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
|
|
||||||
case *Abstraction:
|
case Abstraction:
|
||||||
newParam := e.parameter
|
newParam := e.Parameter()
|
||||||
if e.parameter == target {
|
if e.Parameter() == target {
|
||||||
newParam = newName
|
newParam = newName
|
||||||
}
|
}
|
||||||
|
|
||||||
newBody := Rename(e.body, target, newName)
|
newBody := Rename(e.Body(), target, newName)
|
||||||
|
|
||||||
if newParam == e.parameter && newBody == e.body {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewAbstraction(newParam, newBody)
|
return NewAbstraction(newParam, newBody)
|
||||||
|
|
||||||
case *Application:
|
case Application:
|
||||||
newAbs := Rename(e.abstraction, target, newName)
|
newAbs := Rename(e.Abstraction(), target, newName)
|
||||||
newArg := Rename(e.argument, target, newName)
|
newArg := Rename(e.Argument(), target, newName)
|
||||||
|
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewApplication(newAbs, newArg)
|
return NewApplication(newAbs, newArg)
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
|
// Substitute replaces all free occurrences of the target variable with the replacement expression.
|
||||||
|
// Alpha-renaming is performed automatically to avoid variable capture.
|
||||||
func Substitute(expr Expression, target string, replacement Expression) Expression {
|
func Substitute(expr Expression, target string, replacement Expression) Expression {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case *Variable:
|
case Variable:
|
||||||
if e.value == target {
|
if e.Name() == target {
|
||||||
return replacement
|
return replacement
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
|
|
||||||
case *Abstraction:
|
case Abstraction:
|
||||||
if e.parameter == target {
|
if e.Parameter() == target {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
body := e.body
|
body := e.Body()
|
||||||
param := e.parameter
|
param := e.Parameter()
|
||||||
if IsFreeVariable(param, replacement) {
|
if IsFreeVariable(param, replacement) {
|
||||||
freeVars := GetFreeVariables(replacement)
|
freeVars := GetFreeVariables(replacement)
|
||||||
freeVars.Merge(GetFreeVariables(body))
|
freeVars.Merge(GetFreeVariables(body))
|
||||||
@@ -24,19 +26,12 @@ func Substitute(expr Expression, target string, replacement Expression) Expressi
|
|||||||
}
|
}
|
||||||
|
|
||||||
newBody := Substitute(body, target, replacement)
|
newBody := Substitute(body, target, replacement)
|
||||||
if newBody == body && param == e.parameter {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewAbstraction(param, newBody)
|
return NewAbstraction(param, newBody)
|
||||||
|
|
||||||
case *Application:
|
case Application:
|
||||||
newAbs := Substitute(e.abstraction, target, replacement)
|
newAbs := Substitute(e.Abstraction(), target, replacement)
|
||||||
newArg := Substitute(e.argument, target, replacement)
|
newArg := Substitute(e.Argument(), target, replacement)
|
||||||
|
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewApplication(newAbs, newArg)
|
return NewApplication(newAbs, newArg)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package reducer
|
|
||||||
|
|
||||||
// Event represents lifecycle events during reduction.
|
|
||||||
type Event int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StartEvent is emitted before reduction begins.
|
|
||||||
StartEvent Event = iota
|
|
||||||
// StepEvent is emitted after each reduction step.
|
|
||||||
StepEvent
|
|
||||||
// StopEvent is emitted after reduction completes.
|
|
||||||
StopEvent
|
|
||||||
)
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
|||||||
0 := \f.\x.x
|
|
||||||
inc n := \f x.(f (n f x))
|
|
||||||
exp n m := (m n)
|
|
||||||
print n := (n F X)
|
|
||||||
|
|
||||||
N := (inc (inc (inc (inc (inc (inc 0))))))
|
|
||||||
|
|
||||||
(print (exp N N))
|
|
||||||
Reference in New Issue
Block a user