Recursion 我能做什么;复杂的”;没有let绑定的Coq中的相互递归?

Recursion 我能做什么;复杂的”;没有let绑定的Coq中的相互递归?,recursion,coq,termination,mutual-recursion,Recursion,Coq,Termination,Mutual Recursion,考虑以下一对相互递归的Coq数据类型,它们表示非空的树的林。树的每个分支都有一个额外的布尔标志,我们可以使用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. Argume

考虑以下一对相互递归的Coq数据类型,它们表示非空的
树的
树的每个
分支
都有一个额外的布尔标志,我们可以使用
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.
现在,如果忽略此布尔标志,我们可以编写一对映射函数,将函数应用于
中的每个值,这很好:

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\u bad
调用
mapOKTree\u bad
的参数实际上并不是更小

除了…所有的
mapOKTree\u bad
都是经过一些预处理后的额外步骤。这将永远终止,但Coq看不到这一点。为了说服终止检查器,我们可以定义
mapOKTree\u good
,这是相同的,但是本地
let
-绑定;然后,终止检查器将看穿
let
-绑定,并允许我们定义
mapForest\u good
mapTree\u good
。如果我们想得到
mapOKTree\u good
,我们可以在定义了相互递归函数之后使用一个简单的旧定义,它与
let
-绑定的主体相同:

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的终止检查人员接受
\u bad
变体,或者
\u good
技巧是我所掌握的最好的技巧?一个对我来说确实有效的命令,例如
程序定点
函数
,也是一个完全合理的解决方案。

非常部分的回答:我们可以在定义
mapOKTree\u good
之前,用
mapfreest\u good
参数化的中间定义重构
mapOKTree\u 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.

mapTree\u正常
是否应该调用
mapForest\u正常
,而不是
mapForest\u始终
?正如您所写的,mapOKTree\u good的定义完全是独立的。谢谢,詹妮弗-修复了
mapOKTree\u good
只跳过第一次检查(因为随后
mapfreest\u good
调用
mapTree\u good
)。这是有意的语义吗?@AntonTrunov:一方面,是的(语义是当
isokt
false
时短路);另一方面,这其实并不重要,因为重点是递归问题:-)我这么问是因为如果它不是预期的语义,那么还有另一种泛化,即。通过
isOK
,然后我们将有
定义mapOKTree\u good{a}(f:a->a):Tree a->Tree a:=mapTree\u good f(fun\u=>true)。
而且,是的,我认为在使用全语言编程时,我们应该始终从手头程序的含义开始;)以臭名昭著的
插入
函数为例。