According to this course, all constructors (for inductive types) are injective and disjoint:
...Similar principles apply to all inductively defined types: all constructors are injective, and the values built from distinct constructors are never equal. For lists, the cons constructor is injective and nil is different from every non-empty list. For booleans, true and false are unequal.
(And the inversion
tactic based on this assumption)
I am just wondering how do we know this assumption holds?
How do we know that, e.g., we cannot define natural numbers based on
1) a Successor and maybe a "Double" constructor like this:
Inductive num: Type :=
| O : num
| S : num -> num
| D : num -> num.
and
2) some plus
function so that one number 2
can be reached via two different sequences/routes of constructors, S (S O)
and D (S O)
?
What's the mechanism in Coq that ensures the above will never happen?
P.S.
I am not suggesting the above num
example is possible. I am just wondering what makes it impossible.
Thanks
There is none: the constructors
O
,S
andD
are indeed disjoint and injective but the semantics fornum
s you have in your head is not, as a function, injective.That is why
num
would usually be considered to be a bad representation of the natural numbers: working up-to equivalence is quite annoying.When you define an inductive data type in Coq, you are essentially defining a tree type. Each constructor gives a kind of node that is allowed to occur in your tree, and its arguments determine the children and elements that that node can have. Finally, functions defined on inductive types (with the
match
clause) can check the constructors that were used to produce a value of that type in arbitrary ways. This makes Coq constructors very different from constructors you see in an OO language, for instance. An object constructor is implemented as a regular function that initializes a value of a given type; Coq constructors, on the other hand, are enumerating the possible values that the representation of our type allows. To understand this difference better, we can compare the different functions we can define on an object in a traditional OO language, and on an element of an inductive type in Coq. Let's use yournum
type as an example. Here's an object-oriented definition:And here's a definition in Coq:
In the OO example, when we write a function that takes a
Num
argument, there's no way of knowing which "constructor" was used to produce that value, because this information is not stored in theval
field. In particularNum.doub(Num.succ(Num.zero()))
andNum.succ(Num.succ(Num.zero()))
would be equal values.In the Coq example, on the other hand, things change, because we can determine which constructor was used to form a
num
value, thanks to thematch
statement. For instance, using Coq strings, we could write a function like this:In particular, even though your intended meaning for the constructors implies that
succ (succ zero)
anddoub (succ zero)
should be "morally" equal, we can distinguish them by applying thecons_name
function to them:As a matter of fact, we can use
match
to distinguish betweensucc
anddoub
in arbitrary ways:Now,
a = b
in Coq means that there is no possible way we can distinguish betweena
andb
. The above examples show whydoub (succ zero)
andsucc (succ zero)
cannot be equal, because we can write functions that don't respect the meaning that we had in mind when we wrote that type.This explains why constructors are disjoint. That they are injective is actually also a consequence of pattern-matching. For instance, suppose that we wanted to prove the following statement:
We can begin the proof with
Leading us to
Notice that this goal is by simplification equivalent to
If we do
rewrite H
, we obtainwhich simplifies to
At this point, we can conclude with reflexivity. This technique is quite general, and is actually at the core of what
inversion
does.