perf: implement structural sharing for expression trees #10

Merged
mvhutz merged 5 commits from perf/structural-sharing into main 2026-01-11 02:15:38 +00:00
7 changed files with 104 additions and 59 deletions

View File

@@ -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}
}
/** ------------------------------------------------------------------------- */

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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(')')
}

View File

@@ -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
}
}