docs: document remaining packages and simplify AST types #45

Merged
mvhutz merged 15 commits from docs/rest into main 2026-02-10 01:15:42 +00:00
9 changed files with 121 additions and 123 deletions
Showing only changes of commit c6d7dd56ff - Show all commits

View File

@@ -21,7 +21,7 @@ func encodeAbstraction(n *saccharine.Abstraction) lambda.Expression {
// If the function has no parameters, it is a thunk. Lambda calculus still // If the function has no parameters, it is a thunk. Lambda calculus still
// requires _some_ parameter exists, so generate one. // requires _some_ parameter exists, so generate one.
if len(parameters) == 0 { if len(parameters) == 0 {
freeVars := result.GetFree() freeVars := lambda.GetFree(result)
freshName := lambda.GenerateFreshName(freeVars) freshName := lambda.GenerateFreshName(freeVars)
parameters = append(parameters, freshName) parameters = append(parameters, freshName)
} }
@@ -65,7 +65,7 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio
} }
func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression { func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression {
freshVar := lambda.GenerateFreshName(e.GetFree()) freshVar := lambda.GenerateFreshName(lambda.GetFree(e))
return lambda.Application{ return lambda.Application{
Abstraction: lambda.Abstraction{Parameter: freshVar, Body: e}, Abstraction: lambda.Abstraction{Parameter: freshVar, Body: e},

View File

@@ -18,7 +18,7 @@ func ReduceOnce(e lambda.Expression) (lambda.Expression, bool) {
case lambda.Application: case lambda.Application:
if fn, fnOk := e.Abstraction.(lambda.Abstraction); fnOk { if fn, fnOk := e.Abstraction.(lambda.Abstraction); fnOk {
return fn.Body.Substitute(fn.Parameter, e.Argument), true return lambda.Substitute(fn.Body, fn.Parameter, e.Argument), true
} }
abs, reduced := ReduceOnce(e.Abstraction) abs, reduced := ReduceOnce(e.Abstraction)

View File

@@ -6,14 +6,14 @@ import (
"git.maximhutz.com/max/lambda/pkg/codec" "git.maximhutz.com/max/lambda/pkg/codec"
) )
type Marshaler struct{} type Codec struct{}
func (m Marshaler) Decode(string) (Expression, error) { func (m Codec) Decode(string) (Expression, error) {
return nil, fmt.Errorf("unimplemented") return nil, fmt.Errorf("unimplemented")
} }
func (m Marshaler) Encode(e Expression) (string, error) { func (m Codec) Encode(e Expression) (string, error) {
return e.String(), nil return Stringify(e), nil
} }
var _ codec.Codec[Expression] = (*Marshaler)(nil) var _ codec.Codec[Expression] = (*Codec)(nil)

View File

@@ -1,19 +1,27 @@
package lambda package lambda
import "git.maximhutz.com/max/lambda/pkg/set" import (
"fmt"
func (e Variable) GetFree() set.Set[string] { "git.maximhutz.com/max/lambda/pkg/set"
)
// GetFree returns the set of all free variable names in the expression.
// This function does not mutate the input expression.
// The returned set is newly allocated and can be modified by the caller.
func GetFree(e Expression) set.Set[string] {
switch e := e.(type) {
case Variable:
return set.New(e.Name) return set.New(e.Name)
} case Abstraction:
vars := GetFree(e.Body)
func (e Abstraction) GetFree() set.Set[string] {
vars := e.Body.GetFree()
vars.Remove(e.Parameter) vars.Remove(e.Parameter)
return vars return vars
} case Application:
vars := GetFree(e.Abstraction)
func (e Application) GetFree() set.Set[string] { vars.Merge(GetFree(e.Argument))
vars := e.Abstraction.GetFree()
vars.Merge(e.Argument.GetFree())
return vars return vars
default:
panic(fmt.Errorf("unknown expression type: %v", e))
}
} }

View File

@@ -1,12 +1,18 @@
package lambda package lambda
func (e Variable) IsFree(n string) bool { import "fmt"
return e.Name == n
}
func (e Abstraction) IsFree(n string) bool { // IsFree returns true if the variable name n occurs free in the expression.
return e.Parameter != n && e.Body.IsFree(n) // This function does not mutate the input expression.
} func IsFree(e Expression, n string) bool {
func (e Application) IsFree(n string) bool { switch e := e.(type) {
return e.Abstraction.IsFree(n) || e.Argument.IsFree(n) case Variable:
return e.Name == n
case Abstraction:
return e.Parameter != n && IsFree(e.Body, n)
case Application:
return IsFree(e.Abstraction, n) || IsFree(e.Argument, n)
default:
panic(fmt.Errorf("unknown expression type: %v", e))
}
} }

View File

@@ -1,56 +1,27 @@
package lambda package lambda
import (
"fmt"
"git.maximhutz.com/max/lambda/pkg/set"
)
// Expression is the interface for all lambda calculus expression types. // Expression is the interface for all lambda calculus expression types.
// It embeds the general expr.Expression interface for cross-mode compatibility. // It embeds the general expr.Expression interface for cross-mode compatibility.
type Expression interface { type Expression interface {
fmt.Stringer expression()
// Substitute replaces all free occurrences of the target variable with the
// replacement expression. Alpha-renaming is performed automatically to
// avoid variable capture.
Substitute(target string, replacement Expression) Expression
// GetFree returns the set of all free variable names in the expression.
// This function does not mutate the input expression.
// The returned set is newly allocated and can be modified by the caller.
GetFree() set.Set[string]
// Rename replaces all occurrences of the target variable name with the new name.
Rename(target string, newName string) Expression
// IsFree returns true if the variable name n occurs free in the expression.
// This function does not mutate the input expression.
IsFree(n string) bool
} }
/** ------------------------------------------------------------------------- */
type Abstraction struct { type Abstraction struct {
Parameter string Parameter string
Body Expression Body Expression
} }
var _ Expression = Abstraction{} func (a Abstraction) expression() {}
/** ------------------------------------------------------------------------- */
type Application struct { type Application struct {
Abstraction Expression Abstraction Expression
Argument Expression Argument Expression
} }
var _ Expression = Application{} func (a Application) expression() {}
/** ------------------------------------------------------------------------- */
type Variable struct { type Variable struct {
Name string Name string
} }
var _ Expression = Variable{} func (v Variable) expression() {}

View File

@@ -1,28 +1,31 @@
package lambda package lambda
import "fmt"
// Rename replaces all occurrences of the target variable name with the new name. // Rename replaces all occurrences of the target variable name with the new name.
func (e Variable) Rename(target string, newName string) Expression { func Rename(e Expression, target string, newName string) Expression {
switch e := e.(type) {
case Variable:
if e.Name == target { if e.Name == target {
return Variable{Name: newName} return Variable{Name: newName}
} }
return e return e
} case Abstraction:
func (e Abstraction) Rename(target string, newName string) Expression {
newParam := e.Parameter newParam := e.Parameter
if e.Parameter == target { if e.Parameter == target {
newParam = newName newParam = newName
} }
newBody := e.Body.Rename(target, newName) newBody := Rename(e.Body, target, newName)
return Abstraction{Parameter: newParam, Body: newBody} return Abstraction{Parameter: newParam, Body: newBody}
} case Application:
newAbs := Rename(e.Abstraction, target, newName)
func (e Application) Rename(target string, newName string) Expression { newArg := Rename(e.Argument, target, newName)
newAbs := e.Abstraction.Rename(target, newName)
newArg := e.Argument.Rename(target, newName)
return Application{Abstraction: newAbs, Argument: newArg} return Application{Abstraction: newAbs, Argument: newArg}
default:
panic(fmt.Errorf("unknown expression type: %v", e))
}
} }

View File

@@ -1,13 +1,17 @@
package lambda package lambda
func (a Abstraction) String() string { import "fmt"
return "\\" + a.Parameter + "." + a.Body.String()
}
func (a Application) String() string { // Stringify turns an expression as a string.
return "(" + a.Abstraction.String() + " " + a.Argument.String() + ")" func Stringify(e Expression) string {
} switch e := e.(type) {
case Variable:
func (v Variable) String() string { return e.Name
return v.Name case Abstraction:
return "\\" + e.Parameter + "." + Stringify(e.Body)
case Application:
return "(" + Stringify(e.Abstraction) + " " + Stringify(e.Argument) + ")"
default:
panic(fmt.Errorf("unknown expression type: %v", e))
}
} }

View File

@@ -1,35 +1,41 @@
package lambda package lambda
func (e Variable) Substitute(target string, replacement Expression) Expression { import "fmt"
// Substitute replaces all free occurrences of the target variable with the
// replacement expression. Alpha-renaming is performed automatically to
// avoid variable capture.
func Substitute(e Expression, target string, replacement Expression) Expression {
switch e := e.(type) {
case Variable:
if e.Name == target { if e.Name == target {
return replacement return replacement
} }
return e return e
} case Abstraction:
func (e Abstraction) Substitute(target string, replacement Expression) Expression {
if e.Parameter == target { if e.Parameter == target {
return e return e
} }
body := e.Body body := e.Body
param := e.Parameter param := e.Parameter
if replacement.IsFree(param) { if IsFree(replacement, param) {
freeVars := replacement.GetFree() freeVars := GetFree(replacement)
freeVars.Merge(body.GetFree()) freeVars.Merge(GetFree(body))
freshVar := GenerateFreshName(freeVars) freshVar := GenerateFreshName(freeVars)
body = body.Rename(param, freshVar) body = Rename(body, param, freshVar)
param = freshVar param = freshVar
} }
newBody := body.Substitute(target, replacement) newBody := Substitute(body, target, replacement)
return Abstraction{Parameter: param, Body: newBody} return Abstraction{Parameter: param, Body: newBody}
} case Application:
abs := Substitute(e.Abstraction, target, replacement)
func (e Application) Substitute(target string, replacement Expression) Expression { arg := Substitute(e.Argument, target, replacement)
abs := e.Abstraction.Substitute(target, replacement)
arg := e.Argument.Substitute(target, replacement)
return Application{Abstraction: abs, Argument: arg} return Application{Abstraction: abs, Argument: arg}
default:
panic(fmt.Errorf("unknown expression type: %v", e))
}
} }