定义提取列表前半部分的Fixpoint函数时遭遇“Cannot guess decreasing argument of fix”错误
提取列表前半部分的Fixpoint函数时遭遇“Cannot guess decreasing argument of fix”错误
这个错误的核心原因是Coq对Fixpoint的递归终止检查规则——它默认只认语法上的严格子项关系,没法自动推断出你递归调用时传入的参数确实比原参数“更小”(哪怕它的长度确实更短)。
你原来的代码里,递归调用的是half (rev b),但rev b和原列表l在语法结构上没有直接的子项关联(比如l是h::t,rev b是t去掉最后一个元素后的结果,不是t的直接子项),所以Coq没法确认这个递归一定会终止,就抛出了这个错误。
下面给你两种更稳妥的实现方式,都能通过Coq的递归检查:
方法一:快慢指针法(纯结构递归)
这个思路用两个列表模拟“快慢指针”:快指针每次走两步,慢指针每次走一步,当快指针走到头时,慢指针走过的元素就是我们要的前半部分(完美符合你“奇数长度时排除中间元素”的需求):
Fixpoint half_aux {X : Type} (slow fast : list X) : list X := match fast with | [] => [] (* 快指针空了,说明原列表长度为偶数,慢指针走了一半 *) | _ :: [] => [] (* 快指针剩一个,原列表长度奇数,慢指针走了前半部分(不含中间) *) | _ :: _ :: fast_tail => (* 快指针走两步,慢指针走一步 *) match slow with | [] => [] | h :: slow_tail => h :: half_aux slow_tail fast_tail end end. (* 对外暴露的接口,初始时快慢指针都指向原列表 *) Definition half {X : Type} (l : list X) : list X := half_aux l l.
方法二:基于长度的实现(更直观)
先计算列表长度的一半(自然数除法是向下取整,正好满足奇数长度时排除中间元素的要求),然后取前对应数量的元素:
(* 先实现一个取前n个元素的标准函数 *) Fixpoint take {X : Type} (n : nat) (l : list X) : list X := match n with | 0 => [] | S n' => match l with | [] => [] | h :: t => h :: take n' t end end. (* 计算列表长度的一半,然后取前这么多元素 *) Definition half {X : Type} (l : list X) : list X := take (Nat.div2 (List.length l)) l.
这里Nat.div2是Coq标准库中自然数的半除函数,等价于length l / 2,你也可以用length l // 2(需要启用Nat.div_mod库),效果是一样的。
补充:如果一定要用你原来的思路怎么办?
如果坚持要通过反转列表的方式实现,你需要使用Function命令(需要先导入FunInd库),然后手动证明递归调用时参数的长度是严格递减的——因为Fixpoint只支持结构递归,没法处理这种需要额外终止证明的情况。不过一般来说,上面两种方法已经足够简洁直观了,没必要绕这个弯。
备注:内容来源于stack exchange,提问作者Tyl
相关产品推荐
相关产品推荐

