Dependent-typed terms as return values / function parameters

I’m getting some hard-to-decipher errors regarding type checking on some Coq code that I’m generating. Specifically, I have a dependently-typed function that reads a list from a store and returns it - if the list does not exist in the store, it returns an empty unit list.

This function was easy enough to write in a proof environment, but I’m now running into issues in my auto-generated code. Specifically, after a store is written to in a conditional expression, trying to read a list out of the new store with my dependently-typed function fails. Here’s a MWE, where the last three functions were auto-generated:

Require Import String.
Require Import ZArith.
Require Import List.
Import ListNotations.
Open Scope string_scope.

Inductive value := None | Int (z : Z) | List (T : Set) (l : list T).

Definition store : Type := string -> value.
Definition fresh_store : store := fun _ => None.
Definition update s k v : store :=
    fun x => if String.eqb x k then v else s x.

Definition get_int (s : store) (id : string) : Z :=
    match s id with
    | Int z => z
    | _ => 0
    end.

Definition get_list : 
    forall (s : store) id,
        match s id with 
        | List T _ => list T
        | _ => list unit
        end.
    intros. destruct (s id); try (exact l); exact [].
Defined.

Definition max_idx {T: Type} (l : list T) := Nat.sub (List.length l) 1.

Definition test := update fresh_store "l" (List nat [1;2;3]).
Compute get_list test "l".
(* = [1; 2; 3]
     : match test "l" with
       | List T _ => list T
       | _ => list unit
       end*)
Compute max_idx (get_list test "l").
(* = 2
     : nat*)

(*
Error: In environment
s : store
poison := false : bool
s0 : store
poison0 : bool
The term "get_list s0 "x"" has type
 "match s0 "x"%string with
  | List T _ => list T
  | _ => list unit
  end" while it is expected to have type
 "list ?T@{s0:=s; s:=s0; poison:=poison0}".
*)
Definition culprit_function (s: store)  := 
	let poison : bool := false in
	let (s,poison) := 
		(if (negb poison) then
 			 (update s "x" (List nat [1;2;3]),poison) 
		else
 			(s,true)) in
    max_idx (get_list s "x").

(*
Error: Cannot infer the return type of pattern-matching on
"if negb poison
 then
  (update s "x" (List nat [1; 2; 3]), poison)
 else (s, true)".
*)
Definition culprit_function_simpler (s: store)  := 
    let poison : bool := false in
    let (s,poison) := 
        (if (negb poison) then
                (update s "x" (List nat [1;2;3]),poison) 
        else
                (s,true)) in
    get_list s "x".

(* Compiles fine! *)
Definition non_culprit (s: store)  := 
    let poison : bool := false in
    let (s,poison) := 
        (if (negb poison) then
                (update s "x" (List nat [1;2;3]),poison) 
        else
                (s,true)) in
    get_int s "x".

We can see that retrieving an Int is no problem, but the call to get_list raises an error regarding the type of the output of get_list when it’s passed into another function, and another error regarding pattern-matching type inference when just the output of get_list is returned.

This is my first real foray into dependent typing, so I’m at a loss for why Coq can’t automatically infer that the output of get_list is a list whose type should clearly be fine to pass into max_idx, or why it can’t tell that both branches of the conditional statement return a store * bool. I can’t even seem to coerce type inference with let (s, poison) : store * bool := ... because I get a syntax error regarding :.

I’m also looking into passing in a proof that the output of the store is a List:

Definition get_list' {T : Set} {l : list T} (s : store) id (pf : s id = List T l) :=
    l.

Definition test := update fresh_store "l" (List nat [1;2;3]).
Compute get_list' test "l" eq_refl. (* [1;2;3] *)
Compute max_idx (get_list' test "l" eq_refl). (* 2 *)

But I’m running into similarly-counterintuitive type-inference errors.

All this to say: what can I do next to be able to auto-generate code that can call dependent-typed functions and be able to use their output?

It is not, it’s match something with ... end.

Definition get_list : 
    forall (s : store) id,
        list (match s id with 
        | List T _ => T
        | _ => unit
        end).

may work better.

it can’t tell that both branches of the conditional statement return a store * bool.

That is not the problem (although I agree the error message is confusing). let (s, poison) := foo in bar is the same (up to some differences in type inference) as match foo with (s, poison) => bar end, the error says it can’t infer the return for this match.

In fact I can’t infer it either, what do you expect to get here?

All this to say: what can I do next to be able to auto-generate code that can call dependent-typed functions and be able to use their output?

You need to understand the typing rules enough to not confuse an impossible to reduce match where both branches are list with list.

1 Like

Thank you, your response helped me make sense of a lot of the problem! I can see why my original get_list implementation’s type was ambiguous. Unfortunately, I’m at another impasse: retrieving from these lists:

(* Error: In environment
s : store
poison := false : bool
s0 : store
poison0 : bool
The term "99" has type "nat"
while it is expected to have type
 "match s0 "x" with
  | List T _ => T
  | _ => unit
  end". *)
Definition culprit_function (s: store)  := 
	let poison : bool := false in
	let (s,poison) := 
		(if (negb poison) then
 			 (update s "x" (List nat [1;2;3;4]),poison) 
		else
 			(s,true)) in
    List.nth 3 (get_list s "x") 99.

What I’m getting from this error is that because get_list s "x" has type list (match s0 "x" with ...), the default value passed into List.nth is expected to have the same match ... type. This doesn’t seem to allow for arbitrary retrieval from a list, so I’m hoping there’s some kind of coercion I can do to the default value to force it into a type. Alternatively, there is List.nth_error that does not require a default value, but I don’t think my code generator is sophisticated enough to handle the unwrapping in that case (if that would even let me avoid the type inference issues I’m already having, which I doubt).

An unrelated thing I wasn’t sure about in your response:

That is not the problem (although I agree the error message is confusing). let (s, poison) := foo in bar is the same (up to some differences in type inference) as match foo with (s, poison) => bar end, the error says it can’t infer the return for this match.

In fact I can’t infer it either, what do you expect to get here?

For culprit_function_simpler, I was expecting the following typing:

(if (negb poison) then
                (update s "x" (List nat [1;2;3]),poison) 
        else
                (s,true)) : store * bool

(s,poison) : store * bool

get_list s "x" : list (match s "x" with List T _ => T | _ => unit)

What is coq referring to in regards to a return when it says it can’t infer the return for this match? I’ve seen the return keyword in the syntax for dependent typing but it isn’t clear to me what it’s designating or if that’s what’s being referred to here. I’m still not sure why type inference fails here, which part of the function is ambiguously-typed, or why the equivalence of the let foo := bar in baz notation and match bar with foo => baz end sheds light on the situation.

Thank you for your time!

That’s not the equivalence.
In let foo := bar in baz, baz is typed with foo := bar in the context
In let (foo1, foo2) := bar in baz, baz is typed with abstract variables foo1 and foo2 in the context.