# 如何定义“类型包含”？

**URL:** <https://discourse.rocq-prover.org/t/topic/961>\
**Category:** \[zh\] Rocq 中文\
**Created:** [July 23, 2020, 5:05am UTC](https://discourse.rocq-prover.org/t/topic/961 "2020-07-23T05:05:39Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![hhiim](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/hhiim/32/394_2.png) [@hhiim](https://discourse.rocq-prover.org/u/hhiim)\
**Post date:** [July 23, 2020, 5:05am UTC](https://discourse.rocq-prover.org/t/topic/961/1 "2020-07-23T05:05:39Z")

</div>

我想定义“整数类型”。但是遇到了障碍  
虽然，正数、负数和零并不是同一类型，  
但是，正整数、负整数和零都是整数。  
但是Coq似乎不允许类型之间的包含关系。  
如何定义“整数”？有这样的功能吗？

---

<div class="post-metadata">

**Author:** ![Lys](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/lys/32/48_2.png) [@Lys](https://discourse.rocq-prover.org/u/Lys)\
**Post date:** [July 23, 2020, 5:40am UTC](https://discourse.rocq-prover.org/t/topic/961/2 "2020-07-23T05:40:19Z")

</div>

不甚理解你描述的“包含关系”，但Coq标准库有定义整数[`Z`](https://coq.inria.fr/library/Coq.Numbers.BinNums.html#Z)：

```coq
Inductive Z : Set :=
  | Z0 : Z
  | Zpos : positive -> Z
  | Zneg : positive -> Z.

```

更多函数支持收录于[`ZArith`](https://coq.inria.fr/library/Coq.ZArith.ZArith.html)库。

---

<div class="post-metadata">

**Author:** ![jco](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/jco/32/368_2.png) [@jco](https://discourse.rocq-prover.org/u/jco)\
**Post date:** [July 23, 2020, 8:10am UTC](https://discourse.rocq-prover.org/t/topic/961/3 "2020-07-23T08:10:58Z")

</div>

（不好意思，中文不是我的母语）  
@Lys 懂的比我多得多，但是看你的疑问我觉得可能值得强调，用coq这种系统，一个数学来的“理想”有很多不同的定义……没有“最好”的，不过有的定义的代表性更好，或者认为更好用。比如，上述的Z的定义很正常，但是还能做其他的（比如你自己定的（neg nat, zero, pos nat））——这个要看你面对什么样的证明……其实，他们理想下是“一样”，但是做证明的时候有的定义更好用。我敢来这里用我非常差的中文说这句是因为我自己最近在学习coq，而学习过程中反复看同一个概念，就是没有“完美”的定义……不同的情况下有的定义更方便有的非常不方便。比如，lys分享的Z可能更容易extract到比较快的ocaml（software foundation里面有一个不错的解释）……但是我觉得用那个定义作证明会麻烦一点

请原谅我中文不好

---

<div class="post-metadata">

**Author:** ![hhiim](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/hhiim/32/394_2.png) [@hhiim](https://discourse.rocq-prover.org/u/hhiim)\
**Post date:** [July 23, 2020, 8:16am UTC](https://discourse.rocq-prover.org/t/topic/961/4 "2020-07-23T08:16:49Z")

</div>

**谢谢你的回答！**  
没想到这个小问题会耗了那么久的时间……  
我想写出这样的定义，这样证明会方便些：

- 0 对应 O
- 1 对应 (S O)
- 2 对应 (S (S O))
- -1 对应 (K O)
- -2 对应 (K (K O))

这样定义会十分统一且写证明也方便  
如果Coq无法如此统一的定义整数  
证明和一些函数可能就要多写点代码来处理……

如果真的不行，那么我只能多写点代码了

---

<div class="post-metadata">

**Author:** ![Lys](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/lys/32/48_2.png) [@Lys](https://discourse.rocq-prover.org/u/Lys)\
**Post date:** [July 23, 2020, 8:21am UTC](https://discourse.rocq-prover.org/t/topic/961/5 "2020-07-23T08:21:31Z")

</div>

可修改类型定义使你的表达式变得合法：

```coq
Inductive int :=
| O
| S (n : int)
| K (n : int).

```

上述定义的弊端是一个整数存在多种表示，例如`O`和`S (K O)`均对应0。为避免此种情况，需要将整数定义为依存型（dependent type）：

```coq
Variant signal :=
| zero
| positive
| negative.

Inductive int : signal -> Set :=
| O : int zero
| S : int zero + int positive -> int positive
| K : int zero + int negative -> int negative.

```

但经验表明引入依存型通常使证明更加复杂。

---

<div class="post-metadata">

**Author:** ![hhiim](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/hhiim/32/394_2.png) [@hhiim](https://discourse.rocq-prover.org/u/hhiim)\
**Post date:** [July 23, 2020, 8:44am UTC](https://discourse.rocq-prover.org/t/topic/961/7 "2020-07-23T08:44:21Z")

</div>

不好意思了……可能要麻烦一下  
按照这个定义  
`Compute (S O).`  
报错：  
**The term “O” has type “int zero” while it is expected to have type**  
**"(int zero + int positive)%type".**

---

<div class="post-metadata">

**Author:** ![Lys](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/lys/32/48_2.png) [@Lys](https://discourse.rocq-prover.org/u/Lys)\
**Post date:** [July 23, 2020, 8:53am UTC](https://discourse.rocq-prover.org/t/topic/961/8 "2020-07-23T08:53:39Z")

</div>

后一种类型定义下，1只能写为`S (inl O)`了。

---

<div class="post-metadata">

**Author:** ![dramforever](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/dramforever/32/255_2.png) [@dramforever](https://discourse.rocq-prover.org/u/dramforever)\
**Post date:** [July 24, 2020, 1:17pm UTC](https://discourse.rocq-prover.org/t/topic/961/9 "2020-07-24T13:17:34Z")

</div>

我觉得最好还是顺着类型系统里面类型本身没有包含关系这个事实走，而不是一定要逆过来。对于你这样的需求我们一般是用另一个 `A -> Prop` 这样的 predicate 来表示的，而不是写在类型本身里面。

你可以试试用 `sig`，用一个类型 `A` 和一个对应的 `A -> Prop` 来表示一个受限制的类型。比如 `{ x: Z | x >= 0 }` 就可以表达非负整数，这里可以理解为 `Z` 限定了这个值的形式，而 `x >= 0` 限定了它的性质。其实观察 `sig` 的定义就知道它是一个 `Z` 和一个 `>=` 的证明绑定起来。

还有一个办法是不绑定，需要 `x` 非负整数的时候多接收一个参数 `x >= 0`，效果是一样的。

对于前者我写了一个例子，你可以参考一下，也可以参考 [Program 的文档](https://coq.inria.fr/refman/addendum/program.html)。

```coq
Require Import ZArith Lia Program.

Open Scope Z_scope.

Definition Znat := { x : Z | x >= 0 }.
Definition Zpos := { x : Z | x > 0 }.

(* Manual construction *)

Lemma three_ge_zero : 3 >= 0.
Proof. lia. Qed. (* lia is a solver *)

Definition three_manual : Znat := exist _ 3 three_ge_zero.

(* Tactic *)

Definition three_tactic : Znat.
Proof.
  unfold Znat.
  exists 3.
  lia.
Qed.

(* Program *)
Program Definition three_program : Znat := 3.
(* Proof is automatic *)

(* Functions *)

Program Definition add_pos_nat (a : Zpos) (b : Znat) : Zpos := a + b.

Next Obligation.
  (*
  1 subgoal
  a : Zpos
  b : Znat
  ______________________________________ (1/1)
  ` a + ` b > 0
  *)
  destruct a, b.
  cbn. lia.
Qed.

```

---

<div class="post-metadata">

**Author:** ![lastland](https://sea1.discourse-cdn.com/flex001/user_avatar/discourse.rocq-prover.org/lastland/32/364_2.png) [@lastland](https://discourse.rocq-prover.org/u/lastland)\
**Post date:** [July 27, 2020, 6:27pm UTC](https://discourse.rocq-prover.org/t/topic/961/10 "2020-07-27T18:27:27Z")

</div>

除了上述那些方法，类型包含还可以通过 implicit coercion 来模拟，比如定义 positive 和 Z，然后定义一个函数 pos\_to\_Z 从 positive 转换为 Z，并把这个函数定义为 implicit coercion（见：[https://coq.inria.fr/refman/addendum/implicit-coercions.html](https://coq.inria.fr/refman/addendum/implicit-coercions.html) ）  
这种方式相比于使用 dependent types 的方法，好处是避免了 dependent types（在 Coq 中涉及 dependent types 的证明有时比较麻烦），坏处是有一个需要一定计算量的函数在那里，如果你需要经常从 pos 转到 Z，而且你在乎性能（比如你要导出 Coq 为 OCaml），那么这个方法性能可能会差一点（差多少取决于你的 pos\_to\_Z 函数）。

还有种方法是用 Data Type a la carte（[http://www.cs.ru.nl/~W.Swierstra/Publications/DataTypesALaCarte.pdf](http://www.cs.ru.nl/~W.Swierstra/Publications/DataTypesALaCarte.pdf) ）的方式来定义类型，不过这个有点复杂。
