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 { type Expression interface {
Accept(Visitor) Accept(Visitor)
Copy() Expression
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Abstraction struct { type Abstraction struct {
Parameter string parameter string
Body Expression body Expression
} }
func (a *Abstraction) Copy() Expression { func (a *Abstraction) Parameter() string {
return NewAbstraction(a.Parameter, a.Body.Copy()) return a.parameter
}
func (a *Abstraction) Body() Expression {
return a.body
} }
func (a *Abstraction) Accept(v Visitor) { func (a *Abstraction) Accept(v Visitor) {
@@ -21,36 +24,40 @@ func (a *Abstraction) Accept(v Visitor) {
} }
func NewAbstraction(parameter string, body Expression) *Abstraction { func NewAbstraction(parameter string, body Expression) *Abstraction {
return &Abstraction{Parameter: parameter, Body: body} return &Abstraction{parameter: parameter, body: body}
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Application struct { type Application struct {
Abstraction Expression abstraction Expression
Argument Expression argument Expression
} }
func (a *Application) Copy() Expression { func (a *Application) Abstraction() Expression {
return NewApplication(a.Abstraction.Copy(), a.Argument.Copy()) return a.abstraction
}
func (a *Application) Argument() Expression {
return a.argument
} }
func (a *Application) Accept(v Visitor) { func (a *Application) Accept(v Visitor) {
v.VisitApplication(a) v.VisitApplication(a)
} }
func NewApplication(function Expression, argument Expression) *Application { func NewApplication(abstraction Expression, argument Expression) *Application {
return &Application{Abstraction: function, Argument: argument} return &Application{abstraction: abstraction, argument: argument}
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Variable struct { type Variable struct {
Value string value string
} }
func (v *Variable) Copy() Expression { func (v *Variable) Value() string {
return NewVariable(v.Value) return v.value
} }
func (v *Variable) Accept(visitor Visitor) { func (v *Variable) Accept(visitor Visitor) {
@@ -58,7 +65,7 @@ func (v *Variable) Accept(visitor Visitor) {
} }
func NewVariable(name string) *Variable { 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] { 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.value)
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 nil

View File

@@ -3,11 +3,11 @@ package lambda
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.value == 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
} }

View File

@@ -10,16 +10,16 @@ func ReduceOnce(e *Expression) bool {
switch typed := (*top).(type) { switch typed := (*top).(type) {
case *Abstraction: case *Abstraction:
stack.Push(&typed.Body) stack.Push(&typed.body)
case *Application: case *Application:
if fn, fnOk := typed.Abstraction.(*Abstraction); fnOk { if fn, fnOk := typed.abstraction.(*Abstraction); fnOk {
Substitute(&fn.Body, fn.Parameter, typed.Argument) reduced := Substitute(fn.body, fn.parameter, typed.argument)
*top = fn.Body *top = reduced
return true return true
} }
stack.Push(&typed.Argument) stack.Push(&typed.argument)
stack.Push(&typed.Abstraction) stack.Push(&typed.abstraction)
} }
} }

View File

@@ -1,19 +1,38 @@
package lambda package lambda
func Rename(e Expression, target string, substitute string) { func Rename(expr Expression, target string, newName string) Expression {
switch e := e.(type) { switch e := expr.(type) {
case *Variable: case *Variable:
if e.Value == target { if e.value == target {
e.Value = substitute return NewVariable(newName)
} }
return e
case *Abstraction: case *Abstraction:
if e.Parameter == target { newParam := e.parameter
e.Parameter = substitute 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: case *Application:
Rename(e.Abstraction, target, substitute) newAbs := Rename(e.abstraction, target, newName)
Rename(e.Argument, target, substitute) 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) { func (v *stringifyVisitor) VisitVariable(a *Variable) {
v.builder.WriteString(a.Value) v.builder.WriteString(a.value)
} }
func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) { func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) {
v.builder.WriteRune('\\') v.builder.WriteRune('\\')
v.builder.WriteString(f.Parameter) v.builder.WriteString(f.parameter)
v.builder.WriteRune('.') v.builder.WriteRune('.')
f.Body.Accept(v) f.body.Accept(v)
} }
func (v *stringifyVisitor) VisitApplication(c *Application) { func (v *stringifyVisitor) VisitApplication(c *Application) {
v.builder.WriteRune('(') v.builder.WriteRune('(')
c.Abstraction.Accept(v) c.abstraction.Accept(v)
v.builder.WriteRune(' ') v.builder.WriteRune(' ')
c.Argument.Accept(v) c.argument.Accept(v)
v.builder.WriteRune(')') v.builder.WriteRune(')')
} }

View File

@@ -1,27 +1,46 @@
package lambda package lambda
func Substitute(e *Expression, target string, replacement Expression) { func Substitute(expr Expression, target string, replacement Expression) Expression {
switch typed := (*e).(type) { switch e := expr.(type) {
case *Variable: case *Variable:
if typed.Value == target { if e.value == target {
*e = replacement.Copy() return replacement
} }
return e
case *Abstraction: case *Abstraction:
if typed.Parameter == target { if e.parameter == target {
return return e
} }
if IsFreeVariable(typed.Parameter, replacement) { body := e.body
replacementFreeVars := GetFreeVariables(replacement) param := e.parameter
used := GetFreeVariables(typed.Body) if IsFreeVariable(param, replacement) {
used.Merge(replacementFreeVars) freeVars := GetFreeVariables(replacement)
freshVar := GenerateFreshName(used) freeVars.Merge(GetFreeVariables(body))
Rename(typed, typed.Parameter, freshVar) 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: case *Application:
Substitute(&typed.Abstraction, target, replacement) newAbs := Substitute(e.abstraction, target, replacement)
Substitute(&typed.Argument, target, replacement) newArg := Substitute(e.argument, target, replacement)
if newAbs == e.abstraction && newArg == e.argument {
return e
}
return NewApplication(newAbs, newArg)
default:
return expr
} }
} }