recursion - 我可以在 Coq 中进行 “complex” 相互递归而不需要 let 绑定(bind)吗?

标签 recursion coq termination mutual-recursion

考虑以下一对相互递归的 Coq 数据类型,它们表示非空 TreeForestTree 的每个 Branch 都包含一个额外的 bool 标志,我们可以使用 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.

现在,如果我们忽略这个 bool 标志,我们可以编写一对映射函数来将函数应用于 ForestTree 中的每个值,并且这个工作正常:

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.

但是,假设 bool 值代表某种有效性检查,这在实际代码中会更复杂。因此,我们首先检查 bool 值,只有在必要时才实际递归。这意味着我们有三个相互递归的函数,但其​​中一个只是传递工作。不幸的是,这不起作用:

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 绑定(bind);然后,终止检查器将看透 let 绑定(bind)并允许我们定义 mapForest_goodmapTree_good。如果我们想要得到mapOKTree_good,我们可以在定义了相互递归函数之后使用一个简单的旧定义,它与let绑定(bind)具有相同的主体:

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 FixpointFunction,也是一个完全合理的解决方案。

最佳答案

非常片面的答案:我们可以使用在定义之前由 mapForest_good 参数化的中间定义来重构 mapOKTree_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.

关于recursion - 我可以在 Coq 中进行 “complex” 相互递归而不需要 let 绑定(bind)吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52599324/

相关文章:

javascript - 递归地重新创建嵌套对象数组

javascript - 遍历树并获取每个对象的深度

scala - 如何在不干扰正常终止行为的情况下将 Scala Actor 添加到现有程序?

ios - 当我重新加载表格 View 时,来自调试器 : Terminated due to memory issue, 的消息

set - 如何在coq中定义集合而不将集合定义为元素列表

agda - 终止检查无法证明∃-even′ : ∀ {n : ℕ} → ∃[ m ] ( 2 * m ≡ n) → even n

c - 为什么递归中的全局变量比局部变量使用更多的内存?

python - 用于重命名以空格或句点结尾的文件夹的递归脚本

coq - Coq 中的析取交换性

Coq:归纳列表的问题