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 8 additions and 22 deletions
Showing only changes of commit 17d71da025 - Show all commits

View File

@@ -1,8 +1,6 @@
package lambda
// Expression represents an immutable lambda calculus expression.
// All expression types use structural sharing: operations return
// the same expression pointer when unchanged, reducing allocations.
type Expression interface {
Accept(Visitor)
Substitute(target string, replacement Expression) Expression
@@ -12,7 +10,6 @@ type Expression interface {
/** ------------------------------------------------------------------------- */
// Abstraction represents a lambda abstraction (λx. body).
// Fields are unexported to enforce immutability.
type Abstraction struct {
parameter string
body Expression
@@ -37,7 +34,6 @@ func NewAbstraction(parameter string, body Expression) *Abstraction {
/** ------------------------------------------------------------------------- */
// Application represents function application (f arg).
// Fields are unexported to enforce immutability.
type Application struct {
function Expression
argument Expression
@@ -62,7 +58,6 @@ func NewApplication(function Expression, argument Expression) *Application {
/** ------------------------------------------------------------------------- */
// Variable represents a variable reference.
// Fields are unexported to enforce immutability.
type Variable struct {
value string
}

View File

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

View File

@@ -1,16 +1,14 @@
package lambda
// Rename replaces all occurrences of target variable with newName.
// Uses structural sharing: returns the same expression pointer when unchanged.
func (v *Variable) Rename(target string, newName string) Expression {
if v.value == target {
return NewVariable(newName)
}
return v // unchanged
return v
}
// Rename replaces all occurrences of target variable with newName.
// Uses structural sharing: only allocates when something changes.
func (a *Abstraction) Rename(target string, newName string) Expression {
newParam := a.parameter
if a.parameter == target {
@@ -20,20 +18,19 @@ func (a *Abstraction) Rename(target string, newName string) Expression {
newBody := a.body.Rename(target, newName)
if newParam == a.parameter && newBody == a.body {
return a // unchanged
return a
}
return NewAbstraction(newParam, newBody)
}
// Rename replaces all occurrences of target variable with newName.
// Uses structural sharing: only allocates when something changes.
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 // unchanged
return a
}
return NewApplication(newFunc, newArg)

View File

@@ -1,26 +1,22 @@
package lambda
// Substitute replaces all free occurrences of target with replacement.
// Uses structural sharing: returns the same expression pointer when unchanged.
func (v *Variable) Substitute(target string, replacement Expression) Expression {
if v.value == target {
return replacement // NO COPY - share directly!
return replacement
}
return v // unchanged, return self
return v
}
// Substitute replaces all free occurrences of target with replacement.
// Uses structural sharing and performs alpha conversion to avoid variable capture.
func (a *Abstraction) Substitute(target string, replacement Expression) Expression {
if a.parameter == target {
return a // shadowed, return unchanged
return a
}
// Handle alpha conversion if needed to avoid variable capture
body := a.body
param := a.parameter
if IsFreeVariable(param, replacement) {
// Need to rename to avoid capture
freeVars := GetFreeVariables(replacement)
freeVars.Merge(GetFreeVariables(body))
freshVar := GenerateFreshName(freeVars)
@@ -30,20 +26,19 @@ func (a *Abstraction) Substitute(target string, replacement Expression) Expressi
newBody := body.Substitute(target, replacement)
if newBody == body && param == a.parameter {
return a // STRUCTURAL SHARING: nothing changed
return a
}
return NewAbstraction(param, newBody)
}
// Substitute replaces all free occurrences of target with replacement.
// Uses structural sharing: only allocates when something actually changes.
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 // STRUCTURAL SHARING: nothing changed
return a
}
return NewApplication(newFunc, newArg)