Terms

from dataclasses import dataclass

class Term:
    pass

@dataclass
class App(Term):
    f : str
    args : list["Term"]

dataclass is a nice standard python module to get better printing, etc for python classes by default.

Simplification

Substitution

Substitution of terms vs substitution of variables

@dataclass(frozen=True)
class Var(Term):
    name: str


type Subst = dict[str, Term]


def subst(term: Term, subst: Subst) -> Term:
    match term:
        case Var(name):
            return subst.get(name, term)
        case App(f, args):
            return App(f, tuple(subst(arg, subst) for arg in args))


def pmatch(pattern: Term, term: Term) -> Subst:
    todo = [(pattern, term)]
    subst: Subst = {}
    while todo:
        p, t = todo.pop()
        match p:
            case Var(name):
                if name in subst:
                    if subst[name] != t:
                        return {}
                else:
                    subst[name] = t
            case App(f, args):
                if isinstance(t, App) and f == t.f and len(args) == len(t.args):
                    todo.extend(zip(args, t.args))
                else:
                    return {}

Pattern Matching

I tend to like iterative forms if I don’t need to rebuild something of the same shape as the original term. It matches better the good performance for most languages, and offers greater flexibility for delaying subproblems or examining the todo stack. It also matches the inference rule form of unification nicely.

Unification

Binders

Advanced: Skip on first read. Or I should put into a separate file.

@dataclass(frozen=True)
class BVar(Term):
    idx: int


@dataclass(frozen=True)
class FVar(Term):
    name: str


@dataclass(frozen=True)
class Binder(Term):
    body: Term