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
4 changed files with 77 additions and 75 deletions
Showing only changes of commit a967410af7 - Show all commits

View File

@@ -3,8 +3,6 @@ package lambda
// Expression represents an immutable lambda calculus expression. // Expression represents an immutable lambda calculus expression.
type Expression interface { type Expression interface {
Accept(Visitor) Accept(Visitor)
Substitute(target string, replacement Expression) Expression
Rename(target string, newName string) Expression
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */

View File

@@ -13,7 +13,7 @@ func ReduceOnce(e *Expression) bool {
stack.Push(&typed.body) stack.Push(&typed.body)
case *Application: case *Application:
if fn, fnOk := typed.function.(*Abstraction); fnOk { if fn, fnOk := typed.function.(*Abstraction); fnOk {
reduced := fn.body.Substitute(fn.parameter, typed.argument) reduced := Substitute(fn.body, fn.parameter, typed.argument)
*top = reduced *top = reduced
return true return true
} }

View File

@@ -1,37 +1,39 @@
package lambda package lambda
// Rename replaces all occurrences of target variable with newName. // Rename replaces all occurrences of target variable with newName.
func (v *Variable) Rename(target string, newName string) Expression { func Rename(expr Expression, target string, newName string) Expression {
if v.value == target { switch e := expr.(type) {
return NewVariable(newName) case *Variable:
if e.value == target {
return NewVariable(newName)
}
return e
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
}
return NewAbstraction(newParam, newBody)
case *Application:
newFunc := Rename(e.function, target, newName)
newArg := Rename(e.argument, target, newName)
if newFunc == e.function && newArg == e.argument {
return e
}
return NewApplication(newFunc, newArg)
default:
return expr
} }
return v
}
// Rename replaces all occurrences of target variable with newName.
func (a *Abstraction) Rename(target string, newName string) Expression {
newParam := a.parameter
if a.parameter == target {
newParam = newName
}
newBody := a.body.Rename(target, newName)
if newParam == a.parameter && newBody == a.body {
return a
}
return NewAbstraction(newParam, newBody)
}
// Rename replaces all occurrences of target variable with newName.
func (a *Application) Rename(target string, newName string) Expression {
newFunc := a.function.Rename(target, newName)
newArg := a.argument.Rename(target, newName)
if newFunc == a.function && newArg == a.argument {
return a
}
return NewApplication(newFunc, newArg)
} }

View File

@@ -1,45 +1,47 @@
package lambda package lambda
// Substitute replaces all free occurrences of target with replacement. // Substitute replaces all free occurrences of target with replacement.
func (v *Variable) Substitute(target string, replacement Expression) Expression { func Substitute(expr Expression, target string, replacement Expression) Expression {
if v.value == target { switch e := expr.(type) {
return replacement case *Variable:
if e.value == target {
return replacement
}
return e
case *Abstraction:
if e.parameter == target {
return e
}
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
}
newBody := Substitute(body, target, replacement)
if newBody == body && param == e.parameter {
return e
}
return NewAbstraction(param, newBody)
case *Application:
newFunc := Substitute(e.function, target, replacement)
newArg := Substitute(e.argument, target, replacement)
if newFunc == e.function && newArg == e.argument {
return e
}
return NewApplication(newFunc, newArg)
default:
return expr
} }
return v
}
// Substitute replaces all free occurrences of target with replacement.
func (a *Abstraction) Substitute(target string, replacement Expression) Expression {
if a.parameter == target {
return a
}
body := a.body
param := a.parameter
if IsFreeVariable(param, replacement) {
freeVars := GetFreeVariables(replacement)
freeVars.Merge(GetFreeVariables(body))
freshVar := GenerateFreshName(freeVars)
body = body.Rename(param, freshVar)
param = freshVar
}
newBody := body.Substitute(target, replacement)
if newBody == body && param == a.parameter {
return a
}
return NewAbstraction(param, newBody)
}
// Substitute replaces all free occurrences of target with replacement.
func (a *Application) Substitute(target string, replacement Expression) Expression {
newFunc := a.function.Substitute(target, replacement)
newArg := a.argument.Substitute(target, replacement)
if newFunc == a.function && newArg == a.argument {
return a
}
return NewApplication(newFunc, newArg)
} }