1

I'm stuck while trying to define a recursive function in Coq over a particular type of n-ary tree.

The n-ary tree type I'm using is defined as a mutually inductive type as follows:

Inductive tree : Type :=
| leaf : tree
| node : childs -> tree
with childs : Type :=
| childs_one : tree -> childs
| childs_cons : tree -> childs -> childs.

Note that a node has at least one child. If I want to check that a given Boolean property is verified over all the nodes in a tree, I can write a mutually recursive function to go through the whole structure. However, since the childs type is close to the list tree type, and since the standard library offers a lot of lemmas about lists, I want to convert an instance of the childs type into a list and use the fold_left function to do the computation. The function I want to write is as follows :

Fixpoint check_nodes (chk : tree -> bool) (t : tree) {struct t} : bool := 
  match t with
  | leaf => chk t
  | node chs => fold_left (fun b ti => b && check_nodes chk ti) (childs2list chs) true
  end.

Here, the childs2list function is a trivial function that converts an instance of the childs type into of list of tree. The check_nodes function does not type-check because the Coq kernel is not able to determine that the ti parameter of the lambda function passed to fold_left is a subterm of t.

I haven't yet managed to find a workaround for this problem. I really want to use the fold_left function for lists in the definition of my function. My attempts to define the function with the Fix combinator, and a well-founded relation (based on a "number of nodes" measure) were not successful.

Does anyone have a solution? Or, must I forget about using the fold_left function?

Here is a complete minimal example:

Require Import Bool.
Require Import List.
Import ListNotations.

Inductive tree : Type :=
| leaf : tree
| node : childs -> tree
with childs : Type :=
| childs_nil : childs
| childs_cons : tree -> childs -> childs.

Fixpoint childs2list (chs : childs) {struct chs} : list tree :=
  match chs with
  | childs_nil => nil
  | childs_cons t chs' => t :: childs2list chs'
  end.

Fixpoint check_nodes (chk : tree -> bool) (t : tree) {struct t} : bool := 
  match t with
  | leaf => chk t
  | node chs => fold_left (fun b t__i => b && check_nodes chk t__i) (childs2list chs) true
  end.

Many thanks for taking the time to read my question,

Vincent.

1 Answers1

0

I am afraid there isn't a clean way of achieving exactly what you want. One possibility would be to use a nested list instead of a mutual inductive, and keep the last child as a separate parameter of node. For example:

Require Import Bool.
Require Import List.
Import ListNotations.

Inductive tree :=
| leaf
| node (first : list tree) (last : tree).

Fixpoint check_nodes (chk : tree -> bool) (t : tree) {struct t} : bool :=
  match t with
  | leaf => chk t
  | node first last =>
      fold_left (fun b ti => b && check_nodes chk ti) first (check_nodes chk last)
  end.

Or, if you want to use forallb instead,

Fixpoint check_nodes (chk : tree -> bool) (t : tree) {struct t} : bool :=
  match t with
  | leaf => chk t
  | node first last => forallb (check_nodes chk) first && check_nodes chk last
  end.
Arthur Azevedo De Amorim
  • 23,012
  • 3
  • 33
  • 39
  • I agree with Arthur: once you re-construct a new piece of data, the guard checker cannot keep track of the information that all subterms of this new structure are indeed subterms of the initial argument. – Yves Mar 15 '22 at 07:49
  • Thank you for your answers! I was looking for a solution that would not imply the transformation of the data structure. But I guess there is no way around it... – Vincent Iampietro Apr 12 '22 at 13:23