I am trying to work with proofs about fixed-size bitvectors, or a map from a finite type to bool
. What I'm finding is that "finite type" is actually somewhat nuanced.
There seem to be two standard ways of dealing with finite types:
- Explicitly write each inhabitant as a constructor, as seen with
Byte
type in the standard library - Provide a type indexed by
size: nat
with one constructor that takesn: nat
and proof thatn < size
Explicitly writing each constructor is fine when there is only a few of them, but becomes intractable for a type with more than a few hundred inhabitants. Defining a total order on such a type require something like O(n^2)
lines of code. For example:
(* type with 16 null-ary constructors *)
Inductive regi16 :=
| x0
| x1
| x2
| x3
...
| xd
| xe
| xf
.
Definition regi16lt (x y: regi16) :=
match x with
| x0 =>
match y with
| x0 => False
| _ => True
end
| x1 =>
match y with
| x0 => False
| x1 => False
| _ => True
end
...
| xe =>
match y with
| xf => True
| _ => False
end
| xf => False
end
.
On the other hand, using a type that takes proof as part of its constructor means dragging that proof around everywhere. Example:
Record fintype (n: nat) := mkfin {
val: nat;
range: val < n
}
Lemma zlt: forall n, 0 < S n. Proof. lia. Qed.
Lemma slt: forall n m, n < m -> S n < S m. Proof. lia. Qed.
(* to express the last element of a finite type with 4 inhabitants *)
Check mkfin 4 3 (slt 2 3 (slt 1 2 (slt 0 1 (zlt 0)))).
Is there a better way to work with finite types?