How to make use of information known about this function type in Coq
Question
Say I have the following type typ representing bool or nat:
Inductive typ : Type := TB | TN.
I also have a function to extract an actual function type from a list of typs and a result type:
Fixpoint get_types (s: seq typ) (result_type: Type) : Type := match s with | nil => result_type | x :: xs => match x with | TN => nat -> get_types xs result_type | TB => bool -> get_types xs result_type end end.get_types [:: TB; TN] nat = bool -> nat -> natby []. Qed.get_types [:: TB; TN] nat = bool -> nat -> nat
Now, I have another function that takes as input a list s of typs and a function of type get_types s:
Defining the above function fails at the line | TB :: nil => constructor true.
Given we know here that the type of get_types s nat should be bool -> nat, as the value of s is TB :: nil, I'm wondering if there's a way we can make Coq aware of this so that the above function can be defined?
If not, is this a limitation of Coq or would the same apply to other dependently typed languages?
Edit: For context, this is not the original problem I'm trying to solve; it's a condensed version to show the issue I was having with the type system. In the actual version, rather than hard-coding 2 and true, the typ-like datastructure also carries indices of data to parse from a memory slice, and validation functions. The aim for app is a function that takes a list of such typs, a slice, and a constructor for a record containing such types, then constructs an instance of that record from the indices of the types to parse, or returns None if any of the validations fail.
Answer (Tej Chajed)
There's nothing wrong with what you want in principle. However, at least in Coq, there are some simple rules for how pattern matching is typechecked so that information about which constructor was used can be used in the type. The surface language (Gallina in this case) hides this simplicity by helping compile (or desugar) pattern matches, so that as a user you can write more complex patterns than the underlying system has to deal with. I'm not as familiar with Idris, but based on how complicated dependent pattern matches can be I suspect you run into similar limitations there, where you have to fit your code into a form the system can type check.
Here, you're running into two limitations of this pattern matching compilation. The first is that the type of constructor is not specialized based on the match on s. This is easily fixed by computing a function from get_types s nat -> nat, which the compiler gets right.
Require Import List. Import ListNotations. Inductive typ : Type := TB | TN. Fixpoint get_types (s: list typ) (result_type: Type) : Type := match s with | nil => result_type | x :: xs => match x with | TN => nat -> get_types xs result_type | TB => bool -> get_types xs result_type end end.(* fails due to limitation of termination checker with nested matches *)
...but then you run into a second problem with the termination checker. Note that your match is complex: it analyzes the structure of s as well as its head and tail (if it was built with cons). Ultimately all pattern matches are compiled to nested pattern matches on a single inductive type. If you look at this unfolding, you're destructing s into t :: xs, and then destructing xs again into t0 :: xs', before finally recursing on xs. Unfortunately, the Coq termination checker only sees this as t0 :: xs' and doesn't recognize it as a subterm of s (it really wants xs).
I had a hard time manually writing your function in a way that type checks, but here's a version written using tactics that is functionally correct. Note that the definition it produces is quite a bit more complicated than any ordinary pattern match, because it has to maintain a proof produced by destruct_with_eqn. However, that proof is crucial to simultaneously using xs to make the termination checker happy and revealing t0 :: xs' for type checking the application of the constructor. It may be complicated but you can still run it just fine, as the last line illustrates.
app: forall s : list typ, get_types s nat -> nat
s: list typ
constructor: get_types s natnatapp: forall s : list typ, get_types s nat -> nat
constructor: natnatapp: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
constructor: match t with | TB => bool -> get_types xs nat | TN => nat -> get_types xs nat endnatexact 2.app: forall s : list typ, get_types s nat -> nat
constructor: natnatapp: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
constructor: match t with | TB => bool -> get_types xs nat | TN => nat -> get_types xs nat endnatapp: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
Heql: xs = []
constructor: match t with | TB => bool -> nat | TN => nat -> nat endnatapp: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: match t with | TB => bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat end | TN => nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat end endnatdestruct t; [ exact (constructor true) | exact (constructor 2) ].app: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
Heql: xs = []
constructor: match t with | TB => bool -> nat | TN => nat -> nat endnatapp: forall s : list typ, get_types s nat -> nat
t: typ
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: match t with | TB => bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat end | TN => nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat end endnatapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endnatapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endnatapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endnatapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endget_types xs natexact (constructor true).app: forall s : list typ, get_types s nat -> nat
t0: typ
l: list typ
constructor: bool -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endget_types (t0 :: l) natapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endnatapp: forall s : list typ, get_types s nat -> nat
xs: list typ
t0: typ
l: list typ
Heql: xs = t0 :: l
constructor: nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endget_types xs natexact (constructor 2). Defined.app: forall s : list typ, get_types s nat -> nat
t0: typ
l: list typ
constructor: nat -> match t0 with | TB => bool -> get_types l nat | TN => nat -> get_types l nat endget_types (t0 :: l) nat
Answer (eponier)
Yet two other ways of defining app.
The first one uses tactics, and relies on induction instead of Fixpoint.
s: seq typ
constructor: get_types s natnats: seq typ
constructor: get_types s natnatconstructor: get_types [::] natnatt: typ
xs: seq typ
constructor: get_types (t :: xs) nat
IHxs: get_types xs nat -> natnatconstructor: get_types [::] natnatt: typ
xs: seq typ
constructor: get_types (t :: xs) nat
IHxs: get_types xs nat -> natnatt: typ
xs: seq typ
constructor: get_types (t :: xs) nat
IHxs: get_types xs nat -> natnatt: typ
xs: seq typ
constructor: get_types (t :: xs) nat
IHxs: get_types xs nat -> natnatt: typ
constructor: get_types [:: t] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt: typ
constructor: get_types [:: t] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatconstructor: get_types [:: TB] nat
IHxs: get_types [::] nat -> natnatconstructor: get_types [:: TN] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatconstructor: get_types [:: TB] nat
IHxs: get_types [::] nat -> natnatconstructor: get_types [:: TN] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatconstructor: get_types [:: TN] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatconstructor: get_types [:: TN] nat
IHxs: get_types [::] nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt, t0: typ
xs: seq typ
constructor: get_types [:: t, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt0: typ
xs: seq typ
constructor: get_types [:: TB, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt0: typ
xs: seq typ
constructor: get_types [:: TN, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt0: typ
xs: seq typ
constructor: get_types [:: TB, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt0: typ
xs: seq typ
constructor: get_types [:: TN, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatt0: typ
xs: seq typ
constructor: get_types [:: TN, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnatexact (IHxs (constructor 2)). Defined.t0: typ
xs: seq typ
constructor: get_types [:: TN, t0 & xs] nat
IHxs: get_types (t0 :: xs) nat -> natnat
The second one uses Gallina and complexed pattern-matchings.
Fixpoint app (s: seq typ) : get_types s nat -> nat :=
match s return get_types s nat -> nat with
| nil => fun _ => 2
| x :: xs =>
match xs as xs0 return xs = xs0 -> get_types (x::xs0) nat -> nat with
| nil => fun _ => match x return get_types (x::nil) nat -> nat with
| TB => fun c => c true
| TN => fun c => c 2
end
| _ => fun e => match e in _ = xs1 return get_types (x::xs1) nat -> nat with
| eq_refl =>
match x return get_types (x::xs) nat -> nat with
| TB => fun c => app xs (c true)
| TN => fun c => app xs (c 2)
end
end
end eq_refl
end.
When destructing xs, we remember an equality between the original xs and what it is destructed in. We do not need this equality in the nil branch and discards it with fun _. In the other branch, we pattern-match on the proof of equality (match e), which corresponds to a rewriting using this equality. Inside the eq_refl branch, we can use the original xs and thus make recursive calls allowed by the termination checker. Outside the pattern-match, we return the right type expected by the pattern-matching on xs. The last thing to do is to provide a proof of the equality of xs and xs0, but it is trivially eq_refl.
Answer (ejgallego)
Well, I am not sure what you are really trying to do, but the first step to submit your code into the "convoy" pattern is indeed to add a bit more structure to you type interpretation. If you separate the interpretation of types from the one for list of types you can easily get a skeleton working:
From mathcomp Require Import all_ssreflect. Set Implicit Arguments. Unset Strict Implicit. Unset Printing Implicit Defensive. Inductive Typ := TB | TN. (* Interpretation for types *) Definition iT w : Type := match w with | TB => bool | TN => nat end. (* Default witness *) Definition dw w : iT w := match w with | TB => true | TN => 2 end. Definition get_types (res : Type) := fix gt (args : list Typ) := match args with | [::] => res | [:: w & xs] => iT w -> gt xs end.
Note that I've generalized the return type too as there was no good reason to hardcode the definition to nat. A fun exercise is to modify the above app function and prove it equivalent to the tactic-based version of Tej.