refactor: make lambda expression types immutable
- 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 - Remove pointer equality optimizations (not applicable with immutable values) - Return empty set instead of nil from GetFreeVariables default case
This commit is contained in:
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.
|
||||
package reducer
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
||||
@@ -13,14 +13,14 @@ import (
|
||||
//
|
||||
// Reducers also implement the Emitter interface to allow plugins to observe
|
||||
// reduction lifecycle events (Start, Step, Stop).
|
||||
type Reducer interface {
|
||||
type Interpreter interface {
|
||||
emitter.Emitter[Event]
|
||||
|
||||
// Reduce performs all reduction steps on the expression.
|
||||
// Emits StartEvent before reduction, StepEvent after each step, and
|
||||
// StopEvent after completion.
|
||||
// Returns the final reduced expression.
|
||||
Reduce()
|
||||
// Run a single step. Returns whether the interpreter is complete or not.
|
||||
Step() bool
|
||||
|
||||
// Run until completion.
|
||||
Run()
|
||||
|
||||
// Expression returns the current expression state.
|
||||
Expression() expr.Expression
|
||||
@@ -17,22 +17,22 @@ type Abstraction struct {
|
||||
body Expression
|
||||
}
|
||||
|
||||
var _ Expression = (*Abstraction)(nil)
|
||||
var _ Expression = Abstraction{}
|
||||
|
||||
func (a *Abstraction) Parameter() string {
|
||||
func (a Abstraction) Parameter() string {
|
||||
return a.parameter
|
||||
}
|
||||
|
||||
func (a *Abstraction) Body() Expression {
|
||||
func (a Abstraction) Body() Expression {
|
||||
return a.body
|
||||
}
|
||||
|
||||
func (a *Abstraction) String() string {
|
||||
func (a Abstraction) String() string {
|
||||
return "\\" + a.parameter + "." + a.body.String()
|
||||
}
|
||||
|
||||
func NewAbstraction(parameter string, body Expression) *Abstraction {
|
||||
return &Abstraction{parameter, body}
|
||||
func NewAbstraction(parameter string, body Expression) Abstraction {
|
||||
return Abstraction{parameter, body}
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
@@ -42,40 +42,40 @@ type Application struct {
|
||||
argument Expression
|
||||
}
|
||||
|
||||
var _ Expression = (*Application)(nil)
|
||||
var _ Expression = Application{}
|
||||
|
||||
func (a *Application) Abstraction() Expression {
|
||||
func (a Application) Abstraction() Expression {
|
||||
return a.abstraction
|
||||
}
|
||||
|
||||
func (a *Application) Argument() Expression {
|
||||
func (a Application) Argument() Expression {
|
||||
return a.argument
|
||||
}
|
||||
|
||||
func (a *Application) String() string {
|
||||
func (a Application) String() string {
|
||||
return "(" + a.abstraction.String() + " " + a.argument.String() + ")"
|
||||
}
|
||||
|
||||
func NewApplication(abstraction Expression, argument Expression) *Application {
|
||||
return &Application{abstraction, argument}
|
||||
func NewApplication(abstraction Expression, argument Expression) Application {
|
||||
return Application{abstraction, argument}
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
|
||||
type Variable struct {
|
||||
value string
|
||||
name string
|
||||
}
|
||||
|
||||
var _ Expression = (*Variable)(nil)
|
||||
var _ Expression = Variable{}
|
||||
|
||||
func (v *Variable) Value() string {
|
||||
return v.value
|
||||
func (v Variable) Name() string {
|
||||
return v.name
|
||||
}
|
||||
|
||||
func (v *Variable) String() string {
|
||||
return v.value
|
||||
func (v Variable) String() string {
|
||||
return v.name
|
||||
}
|
||||
|
||||
func NewVariable(name string) *Variable {
|
||||
return &Variable{name}
|
||||
func NewVariable(name string) Variable {
|
||||
return Variable{name}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ import (
|
||||
"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 {
|
||||
for i := uint64(0); ; i++ {
|
||||
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
|
||||
attempt := "_" + string(strconv.AppendUint(nil, ticker, 10))
|
||||
|
||||
if !used.Has(attempt) {
|
||||
return attempt
|
||||
|
||||
@@ -2,19 +2,22 @@ package lambda
|
||||
|
||||
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] {
|
||||
switch e := e.(type) {
|
||||
case *Variable:
|
||||
return set.New(e.value)
|
||||
case *Abstraction:
|
||||
vars := GetFreeVariables(e.body)
|
||||
vars.Remove(e.parameter)
|
||||
case Variable:
|
||||
return set.New(e.Name())
|
||||
case Abstraction:
|
||||
vars := GetFreeVariables(e.Body())
|
||||
vars.Remove(e.Parameter())
|
||||
return vars
|
||||
case *Application:
|
||||
vars := GetFreeVariables(e.abstraction)
|
||||
vars.Merge(GetFreeVariables(e.argument))
|
||||
case Application:
|
||||
vars := GetFreeVariables(e.Abstraction())
|
||||
vars.Merge(GetFreeVariables(e.Argument()))
|
||||
return vars
|
||||
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
|
||||
|
||||
// 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 {
|
||||
switch e := e.(type) {
|
||||
case *Variable:
|
||||
return e.value == n
|
||||
case *Abstraction:
|
||||
return e.parameter != n && IsFreeVariable(n, e.body)
|
||||
case *Application:
|
||||
return IsFreeVariable(n, e.abstraction) || IsFreeVariable(n, e.argument)
|
||||
case Variable:
|
||||
return e.Name() == n
|
||||
case Abstraction:
|
||||
return e.Parameter() != n && IsFreeVariable(n, e.Body())
|
||||
case Application:
|
||||
return IsFreeVariable(n, e.Abstraction()) || IsFreeVariable(n, e.Argument())
|
||||
default:
|
||||
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
|
||||
|
||||
// Rename replaces all occurrences of the target variable name with the new name.
|
||||
func Rename(expr Expression, target string, newName string) Expression {
|
||||
switch e := expr.(type) {
|
||||
case *Variable:
|
||||
if e.value == target {
|
||||
case Variable:
|
||||
if e.Name() == target {
|
||||
return NewVariable(newName)
|
||||
}
|
||||
return e
|
||||
|
||||
case *Abstraction:
|
||||
newParam := e.parameter
|
||||
if e.parameter == target {
|
||||
case Abstraction:
|
||||
newParam := e.Parameter()
|
||||
if e.Parameter() == target {
|
||||
newParam = newName
|
||||
}
|
||||
|
||||
newBody := Rename(e.body, target, newName)
|
||||
|
||||
if newParam == e.parameter && newBody == e.body {
|
||||
return e
|
||||
}
|
||||
newBody := Rename(e.Body(), target, newName)
|
||||
|
||||
return NewAbstraction(newParam, newBody)
|
||||
|
||||
case *Application:
|
||||
newAbs := Rename(e.abstraction, target, newName)
|
||||
newArg := Rename(e.argument, target, newName)
|
||||
|
||||
if newAbs == e.abstraction && newArg == e.argument {
|
||||
return e
|
||||
}
|
||||
case Application:
|
||||
newAbs := Rename(e.Abstraction(), target, newName)
|
||||
newArg := Rename(e.Argument(), target, newName)
|
||||
|
||||
return NewApplication(newAbs, newArg)
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
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 {
|
||||
switch e := expr.(type) {
|
||||
case *Variable:
|
||||
if e.value == target {
|
||||
case Variable:
|
||||
if e.Name() == target {
|
||||
return replacement
|
||||
}
|
||||
return e
|
||||
|
||||
case *Abstraction:
|
||||
if e.parameter == target {
|
||||
case Abstraction:
|
||||
if e.Parameter() == target {
|
||||
return e
|
||||
}
|
||||
|
||||
body := e.body
|
||||
param := e.parameter
|
||||
body := e.Body()
|
||||
param := e.Parameter()
|
||||
if IsFreeVariable(param, replacement) {
|
||||
freeVars := GetFreeVariables(replacement)
|
||||
freeVars.Merge(GetFreeVariables(body))
|
||||
@@ -24,19 +26,12 @@ func Substitute(expr Expression, target string, replacement Expression) Expressi
|
||||
}
|
||||
|
||||
newBody := Substitute(body, target, replacement)
|
||||
if newBody == body && param == e.parameter {
|
||||
return e
|
||||
}
|
||||
|
||||
return NewAbstraction(param, newBody)
|
||||
|
||||
case *Application:
|
||||
newAbs := Substitute(e.abstraction, target, replacement)
|
||||
newArg := Substitute(e.argument, target, replacement)
|
||||
|
||||
if newAbs == e.abstraction && newArg == e.argument {
|
||||
return e
|
||||
}
|
||||
case Application:
|
||||
newAbs := Substitute(e.Abstraction(), target, replacement)
|
||||
newArg := Substitute(e.Argument(), target, replacement)
|
||||
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user