Hi All,
I'm thinking about finally writing the missing MM0 -> MM translator. The MM0 language was always designed to be compatible with metamath, so there is a mostly one to one mapping from MM0 theorems and proofs to MM. But MM0 has some features that do not easily lower, and some intelligence is needed to select the right choice.
Part 1: General translation
First, I will look at translating an arbitrary MM0 database to an MM database, with no particular relation to
set.mm.
1. Variables. In MM0, every theorem declares its own variables and the types of those variables. There is no notion of a global variable. This is sometimes used (especially in the more CS applications) with descriptively named variables like "addr" or "env". How should these be translated? One option is to use MM local variables, i.e. variables in local scope. But this still requires adding a lot of (global) $v declarations for every variable name in every scope, and moreover I think this runs into restrictions with a variable having two types (for example, "a" is used as a wff in the logic part of the peano database and then as a nat variable later on).
* Option 1: Disambiguate all variable names with their sort, i.e. "aW" and "aN". This is simple to implement but not super nice to look at.
* Option 2: Same as 1 but only disambiguate when a conflict is observed. This is better but still not great because common variables like "a" will get disambiguated at all their uses.
* Option 3: Ignore variable names from the source entirely and select from a predefined list of variables of each type, i.e. specify that wff variables should be "ph,ps,ch,th,..." and nat variables should be "a,b,c,..." This requires more configuration work, and it also ignores the names in the source, which may be informative, for example variables named "G" are often used for the context in deduction style theorems, and names like "env" will clearly be clobbered. If we run out of given names we will have to allocate names like "x1, x2, x3" which are even less pleasant.
2. Definitions. In MM0 you can make a definition and there is a primitive step for unfolding them. This is not terribly hard to model in MM using equality proofs, although it requires that every sort have an equality relation on it. In MM0 not all sorts have an equality relation defined on them, and even when there is an explicit equality relation the system does not register this fact explicitly. For example, in peano.mm1 there is an "eq" relation on nat, and enough axioms to prove that every term respects equality (similar to equality in
set.mm), but like metamath there is nothing special about this term constructor and without one of the axioms this theorem would not be true. By contrast the definitional equality relation exists on all sorts, has its own rules separate from theorem application, and every MM0 term respects definitional equality regardless of any axioms that exist.
* Option 1: Represent the definitional equality relation as a new term constructor, one for each sort. Every term constructor gets an axiom saying it respects definitional equality, and then we have to prove automatically for every new definition that it too respects definitional equality. A downside of this is that for types with an equality already we get two equality relations, and what's worse you can't even prove "defeq a b -> a = b" although it is true at the metalevel.
* Option 2: Add configuration so that you can say that a specified relation should be used in preference to adding a new defeq relation, when one exists on the sort. (For a database like
set.mm this is not a problem since every sort has an equality relation except setvar, which is a pure sort and so does not need a defeq relation.) This generally results in more idiomatic MM statements, although it does require identifying all the axioms that the defeq judgment needs, and the system will probably end up reproving a lot of equality theorems that already exist, unless these are also marked.
3. Sort modifiers. MM0 has a number of sort modifiers, which were originally devised to make the translation to MM clearer, so hopefully this should be easy. By default a MM0 sort corresponds to two MM sorts, one for variables and one for terms of that sort. A pure sort is one without any term constructors, so in this case we can skip the term version; and a strict sort is one without any binders, and for this we can skip the variable version. A free sort is one where you can't drop unbound variables; this one is problematic for MM but we can just ignore it in translation, provided the sort itself is nonempty.
4. Delimiters. MM0 has a tokenizer that is slightly more complicated than split(' '). But adding more spaces for disambiguation is totally okay, so I am inclined to solve this by simply adding spaces around token sequences until it matches MM conventions.
5. Notations. MM0 has a precedence grammar that allows you to avoid a lot of parentheses. MM only allows the encoding of pure CFGs.
* Option 1: MM0's grammar is also a CFG, so use that encoding. This is a strawman because the grammar has thousands of nonterminals (one for each precedence level), and to declare this in MM would need one sort for every precedence level (or at least every precedence level in use, which for peano is probably at least 15-20 levels). We would also need a coercion from each lower precedence level to the next higher level, meaning that terms would be bloated with towers of meaningless coercion functions. (I've actually done this sort of thing in metamath before;
dtt.mm contains a precedence grammar so that lambdas and applications don't need unnecessary parentheses. Even there you need at least three levels and it is not at all fun to write manually.)
* Option 2: Live with the extra parentheses. This is idiomatic MM. But still it leaves open the question of how to determine when to insert parentheses. Every MM sort corresponds to one precedence level, so functions like "+" with type nat -> nat -> nat are not precedence changing and hence require parentheses, while functions like "=": nat -> nat -> wff are potentially precedence changing and require parentheses only if the prec(nat) <= prec(wff). It is not clear how to assign precedences to sorts based on the available precedence information in MM0, though (which is associated with term constructors, not sorts). Perhaps this also needs to be provided as configuration. (In
set.mm it's pretty simple: there are only two expression sorts, and prec(wff) < prec(class). In peano.mm0 I'm not really sure what would be appropriate, perhaps prec(wff) < prec(nat) = prec(set).)
* Option 3: Forget about the analysis and just parenthesize everything. This has the advantage of being easy to implement and also easy to prove unambiguous, compared to option 2.
6. Tactics. This is more of an MM1 feature than MM0, but it will be a shame to lose all the tactic scripts. In particular this information might be useful for using MM1 as a MM IDE by means of continuous translation. I already much prefer MM1 to mmj2 for writing metamath style proofs, and especially if MM1 can work in a semi-translated mode where it is compatible in a limited way with pure metamath theorems, you might be able to use MM1 for
set.mm development.
* Option 1: Live with it, drop the tactics, metamath was never designed for this. This is the more idiomatic option, but it may end up with difficult to maintain MM proofs. I don't think this is a very big problem for the existing peano.mm1 proofs, because they have been written with a mind toward efficient MM output, with the exception that peano.mm1 uses ax_mp a lot more than MM normally would, and the *i "inference form" theorems are mostly absent. But a minimize pass should clean this up. In the future, especially if people other than me start writing tactics, it is likely that much more inefficient proofs will start popping up. (I recognize that I care about proof length a bit more than is rational.)
* Option 2: Put the proof scripts in comments next to the metamath proof, possibly with some sigils so that they can be automatically rediscovered by tooling. This solves the problem of recovering the tactic that produced the proof, although it is also likely to go out of date if it is a proof script for a
set.mm proof and people who don't use MM0 subsequently change the proof without changing the comment. It could be checked in CI but this puts another layer of complexity in MM checking that arguably shouldn't be present if it's actually to be a translation.
* Option 3: Try to reconstruct a MM1 proof script from a MM proof object. This sounds really hard to me, particularly when it comes to identifying where tactics have been called, as they don't really leave any trace in the term.
* Option 4: Add comments *inside* a translated MM proof saying where tactics were called. The most advanced version of this might require a change to the MM spec, but we can probably get away with adding comments like "ABAZBABC $( tac1 $) DFAZAABZ" in the compressed proof block.
7. Do blocks. Similarly, the definition of tactics themselves could be translated, although at this point there is really not much to say on the MM side besides "here's some MM1 code, I hope it means something to you because I sure don't know what it is". In MM1 do blocks can also create theorems and this is sort of the pinnacle of automated proof generation; it is very hard to represent the provenance of these proofs in any meaningful way. (For example, in peano_hex.mm1 I have implemented the arithmetic algorithm in hexadecimal, and this requires an addition and multiplication table for all numbers 0-15, so about 256 theorems for each operation. These theorems, things like 7p4e11, are all being generated automatically.)
I think it is fair to say that for many of the results that are being proven in peano.mm1 and all the knock on CS projects that will be proven with respect to PA, it would be nice for soundness purposes to have a translation that targets not some
peano.mm database that is still PA, but rather
set.mm. That would allow things like the MM0 formalization and the MMC verification framework to also exist inside
set.mm. But this comes with its own, additional challenges.
1. Sorts. peano.mm0 has three sorts: wff, nat, and set. These represent booleans, natural numbers, and the powerset of natural numbers respectively, rather like
set.mm's set and class distinction but for finite set theory. The obvious and idiomatic thing is to map wff to wff, nat to NN0, and set to ~P NN0, but this leads to an obvious problem: what it means to map a sort to a set.
Whenever we have a theorem with a nat variable 'a', we need to replace that with a class variable 'A' and an assumption that 'A e. NN0'. Where do we put this assumption?
* Option 1: Add the assumption in a hypothesis. This is the most faithful representation of the original theorem but it limits its applicability unless we use dedth.
* Option 2: Change the theorem to deduction form and then add a hypothesis under the new context. This is simple-ish to implement, but it does mean that some theorems will have two contexts which is not great.
* Option 3: Try to identify if the theorem is already in deduction form and then use that context. More complicated, and also obviously requires the translator to know about how deduction proofs are done in both systems (although for a translation at this level of faithfulness between specifically peano.mm0 + stuff to
set.mm + stuff that's not unreasonable).
* Option 4: Add the assumptions as antecedents on the theorem. I'm not a fan of this option because it requires knowing all the syl*anc theorems and also messes up the statements of theorems.
Also, in many cases we can deduce the typing condition from another typing condition in the MM0 theorem. For example MM0 itself has sets and uses them for explicit typing; if we have (a: nat) and an assumption h: $ G -> a e. (0 .. 16) $ we might want to not generate two typing assumptions on 'a', since 'A e. ( 0 ..^ 16 )' is already sufficient to derive that A e. NN0. We can probably assert this in general for anything that looks like a set in peano.mm0, because if the sort 'set' is interpreted as ~P NN0 then "a e. A" always implies that 'a' is in NN0 because A is syntactically restricted to be a subset of NN0. Of course we have to do the same thing for 'set', and here we might prefer to use "A C_ NN0" instead of "A e. ~P NN0" for being more idiomatic. Again, lots of special case reasoning.
2. Alignment. peano.mm0 defines a lot of basic functions, like addition, multiplication, division, the cantor pairing function, and so on. Some of these have direct analogues, for example we can more or less directly translate "a + b" to "( A + B )", using that + is closed in NN0 for the typing theorem. Others don't: "a - b" in peano.mm0 is truncated subtraction, which is not defined in
set.mm (IIRC) so we would have to create a new function for this or else litter the output with things like if ( B <_ A , ( A - B ) , 0 ) that could grow the statement exponentially. It's a shame that this can't be translated to ( A - B ) directly but unless the theorem has a B <_ A guard in it this probably can't be done.
Other functions exist but have more complicated lowerings. For example peano.mm0 has the 'the' operator for definite description, which has the type set -> nat, while
set.mm has "iota" instead, which is a binder. So that means that we would want to translate "the A" to "iota_ x e. NN0 x e. A" and "the {x | p}" to "iota_ x e. NN0 p", and this binding mismatch would translate to the theorems as well. This problem can be ignored though if we just want any translation and don't go for maximum fidelity.
---
That covers the issues I can currently see with the translation process. I'm curious to see how people feel about the merits of the different options, what would be the most helpful. Many of the points above also apply to MM -> MM translations, for example type guards and alignment also occur when translating
peano.mm to
set.mm or
hol.mm to
set.mm. It would be nice if we had a good story around interpreting MM databases into abstract (set-theoretic?) models and from there into reasonable MM databases like
set.mm.
Mario Carneiro