Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Computation: conversion? and folding

conversion? searches for a proof that can be constructed from a chain of equalities or equivalences. For a general goal, the chain must reach an earlier line or hypothesis. For an equation, it may instead connect the equation’s two sides. The search stores expressions in an e-graph, a data structure that groups expressions known to be equivalent. It repeatedly applies rules to find more equivalences, a process called saturation. This chapter explains saturation and an alternative mode for directed computation.

Reading the suggested proof

The clearest way to see what conversion? does is to read the proof output. In the lambda calculus theory:

Accepting the suggestion replaces l1 with three lines:

l1_1: $ S a = S b $ by suc_congr [#1]
l1_2: $ S a = S a $ by eq_refl
l1: $ S a = S b $ by eq_trans [l1_2, l1_1]

The proof uses the rules introduced in Equality and normalization: the hypothesis rewrites a to b under S through a congruence rule, and reflexivity and transitivity restate the goal from the chain. The goal above is itself an equation, so once its two sides meet in the search space the chain between them is the proof. A goal of any other shape instead has to convert to a hypothesis or an earlier line, and the emitted chain ends with a transport along the sort’s relation, citing that reference. The theory therefore needs the relevant @relation bundle and a @congr rule for each constructor a rewrite must pass through. Converting a goal to an existing reference also requires transport for the goal’s sort.

A reference of the form rel lhs rhs, for a registered relation, can rewrite between its two sides without an annotation. The emitted proof cites the reference directly, as in suc_congr [#1] above. With relation and congruence rules in place, conversion? can therefore use local equations even when no general conversion rules are registered.

Enrolling rewrite schemas: @conversion

Theorems join the rewrite set with @conversion. They carry a direction token:

--| @conversion ltr
axiom contract (a: wff): $ (a ∧ a) ↔ a $;

The direction is ltr (left to right), rtl (right to left), or both. It controls which side search matches and which expressions it creates. For example, applying (a ∧ a) ↔ a left to right removes duplication. More generally, rules that expand expressions can make search explore many unnecessary terms. Prefer simplifying directions where possible.

The compiler checks the annotation when it registers the rule. The conclusion must be rel lhs rhs for the operand sort’s registered relation. The matched side must be a term application that determines every binder used on the other side. Rules may have bound binders and dependency restrictions; search checks those restrictions before applying them.

A rule may have hypotheses. For example, a / a = 1 might require a ≠ 0. Search applies such a rule only when the e-graph already establishes every hypothesis. An equation is established when its sides belong to the same equivalence class. Any other formula must be equivalent to a theorem hypothesis or an earlier proof line.

Search retries a match on later iterations if its hypotheses are not yet established. The matched side must determine every binder used in a hypothesis.

Associativity and commutativity: role certificates

The two structural laws from associativity and commutativity get special treatment, again for efficiency. Instead of a direction, you annotate the law itself:

With both annotations and a @congr rule, search treats nested applications of the operator as a multiset: an unordered collection that keeps duplicates. This avoids exploring each ordering and grouping separately. With only one of the two annotations, search instead applies that law in both directions during ordinary saturation.

This is related to but not the same as @acui. @acui drives the normalizer’s canonical forms during ordinary line checking, while the conversion annotations drive the term representation during search. The natural deduction context carries both kinds of metadata for exactly that reason.

Definitions

A def can enroll its own defining equation, with an orientation:

unfold replaces the defined term with its body. fold matches the body and replaces it with the defined term. both allows either direction. An unannotated definition is not expanded or folded by conversion?. Here saturation unfolds double 0 to 0 + 0, the addition rules finish, and the emitted chain crosses the definition with a single reflexivity line (the $ double 0 = 0 + 0 $ by eq_refl) which the checker closes through ordinary transparent unfolding.

For a def with hidden dummy binders only fold is legal: unfolding would have to invent a variable, which would complicate search significantly. The fold direction binds the dummy to a variable already present in the matched term.

Computation rules: @compute

For computation, applying reductions in a fixed order can be much cheaper than exploring all application orders. @compute registers a rule for this directed process, called folding, instead of general saturation. The lambda-calculus theory uses it for beta, substitution equations, and addition rules.

This works particularly well for terminating, confluent rule sets: reduction stops, and the result does not depend on the order of steps. Untyped β-reduction is not always terminating, so computation still needs search limits. For example:

--| @compute ltr
axiom beta {x: tm} (e: tm x) (a: tm x): $ (λ x. e) · a = [x := a] e $;

Choose one direction, ltr or rtl, to identify the expression to reduce, called the redex. Folding tries rules in declaration order, applies the first new match at each node, and continues reducing the results. Put specific computation rules before general simplification rules.

Goals may contain theorem variables. conversion? leaves those variables unchanged: for example, a zero law can reduce 0 + a to a just as it reduces 0 + S0 to S0.

A rule can be registered for saturation or computation, but not both. Both modes record justifications in the e-graph and emit ordinary proof steps.

Reading a miss

A failed conversion? says how it failed, which sometimes conveys useful information. This cell asks for a conversion that does not exist; put the cursor on the conversion? to run the search, and the report appears under the placeholder:

conversion? search failed: the egraph saturated (8 e-classes, 8 e-nodes,
1 iterations, 0 rule orientations, 0 local equations): no chain of the
enrolled @conversion rewrites connects this goal to any of the 1 pool
references.

A fully saturated search has reached a fixed point: its rules produce no new equivalences. Failure then means that no chain of the registered conversion rules connects the goal’s sides or reaches a reference. It does not establish that the goal is unprovable. Add relevant equations or a useful reference if the search lacks a needed connection.

Failure caused by an iteration or node limit is inconclusive. Try raising the limits for that call, for example with conversion? (iters: 32, nodes: 20000).

The diagnostic also notes when saturation was approximate. In particular, failure with @compute rules is inconclusive: computation follows one reduction order rather than exploring every possible chain.