2

考虑以下一对相互递归的 Coq 数据类型,它们表示Forest非空Tree的 a。每个BranchaTree都有一个额外的布尔标志,我们可以用isOK.

Inductive Forest a : Type
  := Empty    : Forest a
  |  WithTree : Tree a -> Forest a -> Forest a
with Tree a : Type
  := Branch : bool -> a -> Forest a -> Tree a.

Arguments Empty    {_}.
Arguments WithTree {_} _ _.
Arguments Branch   {_} _ _ _.

Definition isOK {a} (t : Tree a) : bool :=
  match t with
  | Branch ok _ _ => ok
  end.

现在,如果我们忽略这个布尔标志,我们可以编写一对映射函数来将一个函数应用于 aForest或 a中的每个值Tree,这很好用:

Fixpoint mapForest_always {a} (f : a -> a) (ts0 : Forest a) {struct ts0} : Forest a :=
  match ts0 with
  | Empty         => Empty
  | WithTree t ts => WithTree (mapTree_always f t) (mapForest_always f ts)
  end
with mapTree_always {a} (f : a -> a) (t : Tree a) {struct t} : Tree a :=
  match t with
  | Branch ok x ts => Branch ok (f x) (mapForest_always f ts)
  end.

但是,假设布尔值表示一些有效性检查,这在实际代码中会更复杂。所以我们首先检查布尔值,只有在必要时才真正递归。这意味着我们有三个相互递归的函数,但其​​中一个只是处理工作。不幸的是,这不起作用:

Fail Fixpoint mapForest_bad {a} (f : a -> a) (ts0 : Forest a) {struct ts0} : Forest a :=
  match ts0 with
  | Empty         => Empty
  | WithTree t ts => WithTree (mapTree_bad f t) (mapForest_bad f ts)
  end
with mapTree_bad {a} (f : a -> a) (t : Tree a) {struct t} : Tree a :=
  if isOK t
  then mapOKTree_bad f t
  else t
with mapOKTree_bad {a} (f : a -> a) (t : Tree a) {struct t} : Tree a :=
  match t with
  | Branch ok x ts => Branch ok (f x) (mapForest_bad f ts)
  end.

问题在于,它mapTree_bad调用了mapOKTree_bad一个实际上并不小的论点。

除了......所有mapOKTree_bad正在做的只是一些预处理后的额外步骤。这永远终止,但 Coq 看不到这一点。为了说服终止检查器,我们可以改为定义mapOKTree_good,它是相同的,但是是本地let绑定;然后,终止检查器将看穿let-binding 并允许我们定义mapForest_goodand mapTree_good。如果我们想得到mapOKTree_good,我们可以在定义相互递归函数之后使用一个普通的旧定义,它与let-binding 具有相同的主体:

Fixpoint mapForest_good {a} (f : a -> a) (ts0 : Forest a) {struct ts0} : Forest a :=
  match ts0 with
  | Empty         => Empty
  | WithTree t ts => WithTree (mapTree_good f t) (mapForest_good f ts)
  end
with mapTree_good {a} (f : a -> a) (t : Tree a) {struct t} : Tree a :=
  let mapOKTree_good {a} (f : a -> a) (t : Tree a) : Tree a :=
        match t with
        | Branch ok x ts => Branch ok (f x) (mapForest_good f ts)
        end in
  if isOK t
  then mapOKTree_good f t
  else t.

Definition mapOKTree_good {a} (f : a -> a) (t : Tree a) : Tree a :=
  match t with
  | Branch ok x ts => Branch ok (f x) (mapForest_good f ts)
  end.

这行得通,但它并不漂亮。有什么方法可以说服 Coq 的终止检查器接受这些_bad变体,还是_good我有最好的技巧?对我有用的命令,例如Program Fixpointor Function,也是一个完全合理的解决方案。

4

1 回答 1

3

非常部分的答案:我们可以重构 的两个定义,mapOKTree_good其中一个中间定义mapForest_good在它被定义之前被参数化。

Definition mapOKTree_good_ {a} mapForest_good
           (f : a -> a) (t : Tree a) : Tree a :=
  match t with
  | Branch ok x ts => Branch ok (f x) (mapForest_good f ts)
  end.

Fixpoint mapForest_good {a} (f : a -> a) (ts0 : Forest a) {struct ts0} : Forest a :=
  match ts0 with
  | Empty         => Empty
  | WithTree t ts => WithTree (mapTree_good f t) (mapForest_good f ts)
  end
with mapTree_good {a} (f : a -> a) (t : Tree a) {struct t} : Tree a :=
  if isOK t
  then mapOKTree_good_ mapForest_good f t
  else t.

Definition mapOKTree_good {a} := @mapOKTree_good_ a mapForest_good.
于 2018-10-01T22:38:03.147 回答