diff --git a/pkg/lambda/expression.go b/pkg/lambda/expression.go index e2814b2..f34b3b4 100644 --- a/pkg/lambda/expression.go +++ b/pkg/lambda/expression.go @@ -2,18 +2,21 @@ package lambda type Expression interface { Accept(Visitor) - Copy() Expression } /** ------------------------------------------------------------------------- */ type Abstraction struct { - Parameter string - Body Expression + parameter string + body Expression } -func (a *Abstraction) Copy() Expression { - return NewAbstraction(a.Parameter, a.Body.Copy()) +func (a *Abstraction) Parameter() string { + return a.parameter +} + +func (a *Abstraction) Body() Expression { + return a.body } func (a *Abstraction) Accept(v Visitor) { @@ -21,36 +24,40 @@ func (a *Abstraction) Accept(v Visitor) { } func NewAbstraction(parameter string, body Expression) *Abstraction { - return &Abstraction{Parameter: parameter, Body: body} + return &Abstraction{parameter: parameter, body: body} } /** ------------------------------------------------------------------------- */ type Application struct { - Abstraction Expression - Argument Expression + abstraction Expression + argument Expression } -func (a *Application) Copy() Expression { - return NewApplication(a.Abstraction.Copy(), a.Argument.Copy()) +func (a *Application) Abstraction() Expression { + return a.abstraction +} + +func (a *Application) Argument() Expression { + return a.argument } func (a *Application) Accept(v Visitor) { v.VisitApplication(a) } -func NewApplication(function Expression, argument Expression) *Application { - return &Application{Abstraction: function, Argument: argument} +func NewApplication(abstraction Expression, argument Expression) *Application { + return &Application{abstraction: abstraction, argument: argument} } /** ------------------------------------------------------------------------- */ type Variable struct { - Value string + value string } -func (v *Variable) Copy() Expression { - return NewVariable(v.Value) +func (v *Variable) Value() string { + return v.value } func (v *Variable) Accept(visitor Visitor) { @@ -58,7 +65,7 @@ func (v *Variable) Accept(visitor Visitor) { } func NewVariable(name string) *Variable { - return &Variable{Value: name} + return &Variable{value: name} } /** ------------------------------------------------------------------------- */ diff --git a/pkg/lambda/get_free_variables.go b/pkg/lambda/get_free_variables.go index ec635e9..1615aac 100644 --- a/pkg/lambda/get_free_variables.go +++ b/pkg/lambda/get_free_variables.go @@ -5,14 +5,14 @@ import "git.maximhutz.com/max/lambda/pkg/set" func GetFreeVariables(e Expression) *set.Set[string] { switch e := e.(type) { case *Variable: - return set.New(e.Value) + return set.New(e.value) case *Abstraction: - vars := GetFreeVariables(e.Body) - vars.Remove(e.Parameter) + vars := GetFreeVariables(e.body) + vars.Remove(e.parameter) return vars case *Application: - vars := GetFreeVariables(e.Abstraction) - vars.Merge(GetFreeVariables(e.Argument)) + vars := GetFreeVariables(e.abstraction) + vars.Merge(GetFreeVariables(e.argument)) return vars default: return nil diff --git a/pkg/lambda/is_free_variable.go b/pkg/lambda/is_free_variable.go index deb3492..abc9b4b 100644 --- a/pkg/lambda/is_free_variable.go +++ b/pkg/lambda/is_free_variable.go @@ -3,11 +3,11 @@ package lambda func IsFreeVariable(n string, e Expression) bool { switch e := e.(type) { case *Variable: - return e.Value == n + return e.value == n case *Abstraction: - return e.Parameter != n && IsFreeVariable(n, e.Body) + return e.parameter != n && IsFreeVariable(n, e.body) case *Application: - return IsFreeVariable(n, e.Abstraction) || IsFreeVariable(n, e.Argument) + return IsFreeVariable(n, e.abstraction) || IsFreeVariable(n, e.argument) default: return false } diff --git a/pkg/lambda/reduce.go b/pkg/lambda/reduce.go index b13d8ed..55d366a 100644 --- a/pkg/lambda/reduce.go +++ b/pkg/lambda/reduce.go @@ -10,16 +10,16 @@ func ReduceOnce(e *Expression) bool { switch typed := (*top).(type) { case *Abstraction: - stack.Push(&typed.Body) + stack.Push(&typed.body) case *Application: - if fn, fnOk := typed.Abstraction.(*Abstraction); fnOk { - Substitute(&fn.Body, fn.Parameter, typed.Argument) - *top = fn.Body + if fn, fnOk := typed.abstraction.(*Abstraction); fnOk { + reduced := Substitute(fn.body, fn.parameter, typed.argument) + *top = reduced return true } - stack.Push(&typed.Argument) - stack.Push(&typed.Abstraction) + stack.Push(&typed.argument) + stack.Push(&typed.abstraction) } } diff --git a/pkg/lambda/rename.go b/pkg/lambda/rename.go index 1cacb9c..bc5625c 100644 --- a/pkg/lambda/rename.go +++ b/pkg/lambda/rename.go @@ -1,19 +1,38 @@ package lambda -func Rename(e Expression, target string, substitute string) { - switch e := e.(type) { +func Rename(expr Expression, target string, newName string) Expression { + switch e := expr.(type) { case *Variable: - if e.Value == target { - e.Value = substitute + if e.value == target { + return NewVariable(newName) } + return e + case *Abstraction: - if e.Parameter == target { - e.Parameter = substitute + newParam := e.parameter + if e.parameter == target { + newParam = newName } - Rename(e.Body, target, substitute) + newBody := Rename(e.body, target, newName) + + if newParam == e.parameter && newBody == e.body { + return e + } + + return NewAbstraction(newParam, newBody) + case *Application: - Rename(e.Abstraction, target, substitute) - Rename(e.Argument, target, substitute) + newAbs := Rename(e.abstraction, target, newName) + newArg := Rename(e.argument, target, newName) + + if newAbs == e.abstraction && newArg == e.argument { + return e + } + + return NewApplication(newAbs, newArg) + + default: + return expr } } diff --git a/pkg/lambda/stringify.go b/pkg/lambda/stringify.go index 0343fcc..1d838b2 100644 --- a/pkg/lambda/stringify.go +++ b/pkg/lambda/stringify.go @@ -7,21 +7,21 @@ type stringifyVisitor struct { } func (v *stringifyVisitor) VisitVariable(a *Variable) { - v.builder.WriteString(a.Value) + v.builder.WriteString(a.value) } func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) { v.builder.WriteRune('\\') - v.builder.WriteString(f.Parameter) + v.builder.WriteString(f.parameter) v.builder.WriteRune('.') - f.Body.Accept(v) + f.body.Accept(v) } func (v *stringifyVisitor) VisitApplication(c *Application) { v.builder.WriteRune('(') - c.Abstraction.Accept(v) + c.abstraction.Accept(v) v.builder.WriteRune(' ') - c.Argument.Accept(v) + c.argument.Accept(v) v.builder.WriteRune(')') } diff --git a/pkg/lambda/substitute.go b/pkg/lambda/substitute.go index 8798165..fcfb043 100644 --- a/pkg/lambda/substitute.go +++ b/pkg/lambda/substitute.go @@ -1,27 +1,46 @@ package lambda -func Substitute(e *Expression, target string, replacement Expression) { - switch typed := (*e).(type) { +func Substitute(expr Expression, target string, replacement Expression) Expression { + switch e := expr.(type) { case *Variable: - if typed.Value == target { - *e = replacement.Copy() + if e.value == target { + return replacement } + return e + case *Abstraction: - if typed.Parameter == target { - return + if e.parameter == target { + return e } - if IsFreeVariable(typed.Parameter, replacement) { - replacementFreeVars := GetFreeVariables(replacement) - used := GetFreeVariables(typed.Body) - used.Merge(replacementFreeVars) - freshVar := GenerateFreshName(used) - Rename(typed, typed.Parameter, freshVar) + body := e.body + param := e.parameter + if IsFreeVariable(param, replacement) { + freeVars := GetFreeVariables(replacement) + freeVars.Merge(GetFreeVariables(body)) + freshVar := GenerateFreshName(freeVars) + body = Rename(body, param, freshVar) + param = freshVar } - Substitute(&typed.Body, target, replacement) + newBody := Substitute(body, target, replacement) + if newBody == body && param == e.parameter { + return e + } + + return NewAbstraction(param, newBody) + case *Application: - Substitute(&typed.Abstraction, target, replacement) - Substitute(&typed.Argument, target, replacement) + newAbs := Substitute(e.abstraction, target, replacement) + newArg := Substitute(e.argument, target, replacement) + + if newAbs == e.abstraction && newArg == e.argument { + return e + } + + return NewApplication(newAbs, newArg) + + default: + return expr } }