有限层上的良序

可以直接阅读本章,也可以通过阅读指南和依赖地图选择其他路线。

阅读指南 · 依赖地图

本章证明每个以数码为索引的层都是有穷的,并以最先分歧赋予其良序;随后结合层号与局部序来良序化极限层。

先前的选择构造为一个族的每一格定位了该格首次拥有成员的层,并证明了它是一个后继。于是该格中恰在那里现身的每个成员,都是同一个集合的可定义子集:一个写在单一层之上的名字。尚缺的是比较这些名字的办法,而本章要在塔的底部造出的正是这种比较。

本章依赖两个论断。第一,凡以数码为索引的层都是有穷的,其确切含义见下文:它附带一份有穷的集合清单,清单包含它的全部成员。第二,有穷层带有一个良序:比较两个成员时,看它们最先在何处出现分歧,并把较大的位置判给含有该处的那一个。

第二个论断才是数学内容所在,它本质上是关于有穷集合的论断。若把同一构造用于自然数的子集,就会出现无穷下降:先是全体自然数,然后是从一开始的全体,再是从二开始的全体,如此下去,每一步删去尚存者中最先的那一个,因而严格落到更低处。构造本身并不排除这种情形;在有穷基底上,只有有穷多个子集,因此寻找最小成员的过程会终止。下文据此证明良基性:一份有穷清单加上一个线序,可以为任何非空性质给出最小成员,方法是逐项检查清单,并在每一步保留截至该处最小的候选;而「每个非空性质都有最小成员」在经典意义下就是良基性。

有穷性能沿塔逐层推广,是因为有穷集合的可定义子集就是它的全部子集,而带清单的集合只有有穷多个子集,清单上的每个位向量对应其中一个。于是一层的清单给出下一层的清单,递归便足以推进整个构造。

极限层的构造无须假设或证明各有穷层序之间相容。它先比较元素首次出现的层号;层号相同,才使用该层自己的序。因此,不同层的元素由层号比较,同一层首次出现的元素由局部序比较。

讨论的舞台是建立在累积层级 $V$ 之上的可构造宇宙。排中律在这里作为显式假设出现:整个模块由一个参数 lem 给出,它对层级 ℓ-suc ℓ 上的每个命题作出判定。本章需要的正是这一个层级,下文的所有构造都可以使用这一固定判定;对于其他层级上的命题,除已证明的定理所述内容外,不作任何论断。

{-# OPTIONS --cubical --safe --guardedness #-}

open import Base.Prelude
open import Base.Classical using ( LEM )

module L.Choice.FiniteStageOrders { : Level} (lem : LEM (ℓ-suc )) where

下文使用的名称都来自可构造层级:塔的层 Lset α、产生一层的全部可定义子集的算子 𝒟ₒ,以及数码 # n 是序数这一事实 numeral-ord。于是每个有限层 Lset (# n) 都是真正的层,这正是后续各节的递归能沿数码攀爬的原因。这里还引入了 Lset-sucFinOf 相关工具,它们把一层与其内部的有穷集合联系起来。

open import FOL.ZFStructure using ( module hPropStructure )
open import V.Hierarchy {} using ( 𝒮ᵥ; extensionalV )
open import L.Constructible {} using ( IsOrd; Lset; Lset-out; 𝒟ₒ; 𝒟ₒ∋⊆ )
open import L.Ordinal {} using ( numeral-ord )
open import L.Axioms.Basic {}

比较需要一个满足三分律的基底序。自然数上的序 natOrder 是一个严格强良基的线序,打包为 SWO,其三情形比较 Tri 分为 lteqgt 三种。后面各节的搜索程序都针对这一接口编写,因此适用于任何 SWO;而自然数的实例正是用来给数码排序的那一个。

  using ( finSet; finSet-in; finSet-out; Lset-suc; module FinOf )
open import L.WellOrder.Base {ℓ-suc }
  using ( Tri; lt; eq; gt; SWO; IsLeast; leastOf; natOrder )

open import Cubical.Data.Bool using ( Bool; true; false; false≢true )
open import Cubical.Data.Nat using ( _+_ )

布尔值在这里作为掩码出现:要枚举带点名册的集合的子集,就把每个条目保留或丢弃,用 Bool 上的 truefalse 记录,而 false≢true 保证二者可区分。在索引一侧,自然数用严格序 _<_ 比较,它是传递且良基的,由 ¬m<m 排除自环,并可用 _≟_ 判定相等。这些恰好是找出见证某性质的最小下标所需的性质,也是扫描中作出逐步判定所需的性质。

open import Cubical.Data.Nat.Order using ( _<_; <-trans; ¬m<m; <-wellfounded; _≟_ )
import Cubical.Data.Nat.Order as NatOrder
open import Cubical.Data.Sigma using ( Σ≡Prop )
open import Cubical.Data.Sum using ( _⊎_; inl; inr )
import Cubical.Data.Empty as Empty

这里的良基性由可达性谓词 Acc 表达:一个点是可达的,当且仅当它的每个前驱都可达,由构造子 acc 封装。当关系的一切点都可达时,称它具有 WellFounded 类型。Acc 上的证明义务都是命题,这一事实由 isPropAcc 记录,并在从「仅仅存在」的数据消去到可达性陈述时用到。模块 WFI 提供消费良基关系的递归原理。

import Cubical.HITs.PropositionalTruncation as PT
open PT using ( ∣_∣₁; ∥_∥₁ )
open import Cubical.Functions.Logic using ( ⇔toPath )
open import Cubical.Induction.WellFounded
  using ( Acc; acc; WellFounded; isPropAcc; module WFI )

对累积层级中的集合 x⟪ x ⟫ 是它的小呈现类型,⟪ x ⟫↪ 把该类型嵌入层级。等价 ∈∈ₛ 联系呈现中的成员关系与层级成员关系,∈-asFiber 则从成员证明恢复索引及其等同路径。空集给出第零层,冯·诺伊曼数码 # n 及其极限 ω 用来索引诸有穷层与极限。

open import Cubical.Relation.Nullary using ( isProp¬ )
open import Cubical.HITs.CumulativeHierarchy.Properties
  using ( ∈∈ₛ; ∈-asFiber; ⟪_⟫; ⟪_⟫↪ )
open import Cubical.HITs.CumulativeHierarchy.Constructions
  using ( ; ∅-empty; module InfinitySet )

下文的隶属陈述取命题为值。因此,⟨ x ∈ˢ A ⟩x 属于 A 的证据类型;点名册用这一形式证明每个列出项确实属于集合,并陈述每个成员都被表示。

open InfinitySet using ( #_; ω )

open hPropStructure 𝒮ᵥ

点名册

Tally 用一个有穷索引族呈现集合的每个成员,允许重复,也不要求单射性或可判定相等。

有穷性以点名册的形式引入:取一个数和一个由相应多个集合组成的族,族中的每个集合都属于 A,并要求「A 的每个成员都仅仅等于其中某一个」。onto 表示该族列出了 A 的所有成员。

重复与不可判定的相等都不造成困难。扫描可以再次遇到同一元素,位向量也按位置记录取舍,即使两个位置名指同一集合亦然。因此,这种刻意保持较弱的有穷性概念能在下一层的构造中保持下去。

集合 A 的点名册有三个数据字段:数 size 决定列出的条目数,item 把每个合法位置 (即 Fin size 的元素) 映为集合 item i,而字段 inside 证明每个被列出的条目确实属于 A。没有这一条,更长的清单会平凡地覆盖较小的集合。注意同一元素完全可能出现在多个位置上:record 并不禁止这一点,也没有任何字段询问两个位置上的集合是否相同。

record Tally (A : S) : Type (ℓ-suc ) where
  field
    size   : 
    item   : Fin size  S
    inside : (i : Fin size)   item i ∈ˢ A 

第四个字段陈述覆盖性。给定 x 及其属于 A 的证明,onto 返回「索引 i路径 item i ≡ x」的命题截断。因此,只能得到某个索引仅仅存在,而不会暴露一个选定位置。后文只在目标为命题时消去这份截断见证。

    onto   : (x : S)   x ∈ˢ A    Σ[ i  Fin size ] (item i  x) ∥₁

劈开一个有穷索引

splitFinjoinFin 把小于和数的索引与某个加数中的索引对应起来,提供枚举掩码所需的算术。

为幂集清点需要枚举位向量,而长度为 n + 1 的向量个数是长度为 n 的两倍。因此需要一项索引算术:小于 a + b 的索引对应于小于 a 的索引或小于 b 的索引,反之亦然。后文只使用其中一个方向,所以这里只证明该方向;bumpLeft 是使沿 a 的递归通过类型检查所需的移位。

一个具体的图景有帮助。取 a = 2b = 3,小于 5 的索引恰好等于「小于 2 的索引或小于 3 的索引」:joinFin 把左加数放进前两个位置、把右加数放进后三个位置,而 splitFin 询问一个索引落入哪个区域。这里与重复无关,因为这两张图关心的是位置,而不是日后放在位置上的条目。

第一张图处理左端增加一的和。bumpLeft 取一个属于 ab 的索引,给出一个属于 suc ab 的索引:左边的索引被外推一格,右边的原样保留。它本身没有内容,存在的原因只是 splitFin 的递归步会从左加数剥掉一格,需要一个移位把左索引放回正确的类型。注意 joinFin 只给出了从 Fin a ⊎ Fin bFin (a + b) 的方向,且 a 显式给出,以便递归能对它作模式匹配。

bumpLeft : {a b : }  Fin a  Fin b  Fin (suc a)  Fin b
bumpLeft (inl i) = inl (suc i)
bumpLeft (inr j) = inr j

joinFin : (a : ) {b : }  Fin a  Fin b  Fin (a + b)
joinFin zero    (inr j)       = j

joinFinsplitFin 形状上互为逆映射,不过后文只证明一个方向的往返。joinFin 沿 a 递归:a 为零时,小于 0 + b 的索引就是小于 b 的索引;a 为后继时,第一个位置属于左加数,于是位置为零的左索引映到零号位置,其余一律上移一格。splitFin 沿同一递归倒着走:小于 a + b 的索引先问它是否小于 a,后继情形用 bumpLeft 恢复被剥掉的类型。

joinFin (suc a) (inl zero)    = zero
joinFin (suc a) (inl (suc i)) = suc (joinFin a (inl i))
joinFin (suc a) (inr j)       = suc (joinFin a (inr j))

splitFin : (a : ) {b : }  Fin (a + b)  Fin a  Fin b
splitFin zero    j       = inr j

往返 split-join 说的是:对刚刚拼合的索引再作劈分,就回到原来的左或右索引。每条子句要么是 refl,要么是对递归路径施用 congsplitFin (joinFin x) 的计算已经归约到对递归答案施加 bumpLeft,而 cong bumpLeft 把归纳假设穿过这一移位。相反的复合从未被断言,这里也没有任何关于「拼合是单射」的主张。

splitFin (suc a) zero    = inl zero
splitFin (suc a) (suc i) = bumpLeft (splitFin a i)

split-join : (a : ) {b : } (x : Fin a  Fin b)  splitFin a (joinFin a x)  x
split-join zero    (inr j)       = refl
split-join (suc a) (inl zero)    = refl

这一算术给掩码一节带来的是对规模的精确记账。当长度 suc n 的掩码枚举在 maskCount n 处把索引一分为二时,splitFin 判定首位是 false 还是 true,并把剩下的索引交给 n 处的递归;mask-ontosplit-join 合起来证明每个位向量都被触及。

split-join (suc a) (inl (suc i)) = cong bumpLeft (split-join a (inl i))
split-join (suc a) (inr j)       = cong bumpLeft (split-join a (inr j))

枚举掩码

maskAt 枚举固定长度的全部布尔向量,而 mask-onto 证明每种选取模式都会出现。

长度为 n掩码是一个 n 位向量;对已经清点的集合,它指明保留哪些条目。共有 maskCount n 个掩码,即二的 n 次幂,这里写成反复加倍的形式。maskAt 把索引解释为掩码:按索引属于两个加数中的哪一支确定首位,再由该支中的剩余索引确定尾部。每个掩码都由某个索引得到,这就是 mask-onto,也是后文使用该枚举所需的唯一性质;该枚举不要求逐点单射。

n = 2,四个索引给出从 false ∷ false ∷ []true ∷ true ∷ [] 的四个掩码。这个构造事实上无重复地枚举它们;不过后面的点名册论证只使用已证明的覆盖性 mask-onto,并不依赖单射性。

掩码的计数按「将来枚举它的那个递归」来定义:长度为零恰有一个掩码;长度为 suc n 的掩码是一个首位加上一个长度为 n 的掩码,故计数为 maskCount n + maskCount n。这就是写成反复加倍形式的二的 n 次幂,而两个加数相同,恰好正是 splitFin 所期待的形状。

maskCount :   
maskCount zero    = 1
maskCount (suc n) = maskCount n + maskCount n

maskCons : (n : )  (Fin (maskCount n)  Vec Bool n)
          Fin (maskCount n)  Fin (maskCount n)  Vec Bool (suc n)

maskCons 把一个首位接到从索引相应半支读出的尾部上:左加数取 false,右加数取 true。于是 maskAt 把索引读成掩码:长度为零时唯一的掩码是空向量;长度为 suc n 时,小于 maskCount (suc n) = maskCount n + maskCount n 的索引被一分为二,所在的半支给出首位,内层索引给出尾部。这个读法是一个定义而非定理:它只是按规则计算。

maskCons n r (inl j) = false  r j
maskCons n r (inr j) = true   r j

maskAt : (n : )  Fin (maskCount n)  Vec Bool n
maskAt zero    j = []
maskAt (suc n) j = maskCons n (maskAt n) (splitFin (maskCount n) j)

覆盖性是 mask-onto 的内容,而它有意不带截断:给定一个向量 v,该陈述产生一个真实的索引,连同从该索引读出的掩码到 v路径。基情形中,空向量来自第零号索引。这是整个枚举中唯一必须交付数据而非仅仅存在性的地方,而它之所以能做到,是因为递归沿着向量本身进行。

mask-onto : (n : ) (v : Vec Bool n)  Σ[ j  Fin (maskCount n) ] (maskAt n j  v)
mask-onto zero    []          = zero , refl
mask-onto (suc n) (false  v) =
  joinFin (maskCount n) (inl (mask-onto n v .fst))
  , (cong (maskCons n (maskAt n)) (split-join (maskCount n) (inl (mask-onto n v .fst)))

后继情形由向量决定分支。首位为 false 时,尾部的索引经 joinFin 拼入左半支;路径分两步拼装:先用 split-join 证明对拼合索引的劈分确实还原出左半支,再用 cong (false ∷_) 把递归得到的路径带上首位。true 的情形逐字相同,只是换成右半支。结合计数,这说明已清点集合的掩码被 Fin (maskCount size) 覆盖,恰好是 Tally 字段所期待的形状。

      cong (false ∷_) (mask-onto n v .snd))
mask-onto (suc n) (true  v)  =
  joinFin (maskCount n) (inr (mask-onto n v .fst))
  , (cong (maskCons n (maskAt n)) (split-join (maskCount n) (inr (mask-onto n v .fst)))
      cong (true ∷_) (mask-onto n v .snd))

选出一个子族

select 按布尔掩码筛选一个有穷族,其成员引理则把选中的条目与标为真的位置对应起来。

select 把掩码作用到一个族上:它保留那些位为 true 的条目,并把它们重新组成一个族,同时给出该族的长度。长度是由递归产生的,这正是关键:无须计数,也不需要任何算术把答案与掩码联系起来。

两条规格说明各自刻画结果包含什么,且都不带截断,因为二者都是同一次递归的直接推论。marks 的方向相反:它把对诸条目的一次判定变成记录该判定的掩码。

一个小例子显示了与重复的交互。取一个含重复条目的族,并取保留两个副本的掩码:选出的子族便两次含有该条目,两条副本各由引理以各自的原始位置回答。没有任何东西被丢失或合并,因为从来没有任何东西被要求唯一。

辅助函数 selectStep 完成筛选的一步:给定条目 x 与已选好的族,它把 x 排在最前并报告新长度 suc k。其结果类型把族与长度打包成一个依赖对,于是递归可以增长长度而不必对掩码做任何算术。

selectStep : {ℓ' : Level} {X : Type ℓ'}  X  Σ[ k   ] (Fin k  X)
            Σ[ k   ] (Fin k  X)
selectStep {X = X} x (k , g) = suc k , h
  where
  h : Fin (suc k)  X

select 是沿掩码的递归。空掩码什么也不选,用荒谬模式表达:长度为零的族没有任何位置。首位为 false 时丢弃头部并沿右移后的族递归;首位为 true 时用 selectStep 保留头部。每一步族都右移一格,这正是全篇出现的 λ i → f (suc i) 所记录的内容。

  h zero    = x
  h (suc i) = g i

select : {ℓ' : Level} {X : Type ℓ'} (n : )  (Fin n  X)  Vec Bool n
        Σ[ k   ] (Fin k  X)
select zero    f v           = zero , λ ()

第一条规格 select-out 顺向读出选取结果:被选族的每个位置 j 都来自某个位为 true 的原始位置 i,且该处的条目确实是原来的条目 f i。这一主张是数据而非仅仅的存在性:实际产生一个见证 i,位与等式都显式给出。

select (suc n) f (false  v) = select n  i  f (suc i)) v
select (suc n) f (true  v)  = selectStep (f zero) (select n  i  f (suc i)) v)

select-out : {ℓ' : Level} {X : Type ℓ'} (n : ) (f : Fin n  X) (v : Vec Bool n)
             (j : Fin (select n f v .fst))
            Σ[ i  Fin n ] ((lookup i v  true) × (select n f v .snd j  f i))

证明沿与定义相同的递归走。false 情形中头部已被丢弃,于是在尾部回答 j 的原始位置要上移成整向量中的 suc i;局部的 step 恰好完成对见证三元组的这一簿记。

select-out zero    f []          ()
select-out (suc n) f (false  v) j       = step (select-out n  i  f (suc i)) v j)
  where
  step : Σ[ i  Fin n ] ((lookup i v  true)
           × (select n  i  f (suc i)) v .snd j  f (suc i)))

true 情形分两个子情形。若被选位置是第一个,答案就是头部本身,两条等式都因 select 把头部原封不动作为零号位置返回而由 refl 成立;否则递归回答尾部的位置,同样的上移照旧适用。

        Σ[ i  Fin (suc n) ] ((lookup i (false  v)  true)
           × (select (suc n) f (false  v) .snd j  f i))
  step (i , e , q) = suc i , (e , q)
select-out (suc n) f (true  v)  zero    = zero , (refl , refl)
select-out (suc n) f (true  v)  (suc j) = step (select-out n  i  f (suc i)) v j)

第二个子情形重复同样的上移簿记,只是此时头部仍在:true ∷ v 的被选族是头部接上尾部的选取结果,因此头部之后的位置在尾部得到回答并映回 suc i。两个分支只在这一重定位上不同,这正是它们各自需要一个 step 的原因。

  where
  step : Σ[ i  Fin n ] ((lookup i v  true)
           × (select n  i  f (suc i)) v .snd j  f (suc i)))
        Σ[ i  Fin (suc n) ] ((lookup i (true  v)  true)
           × (select (suc n) f (true  v) .snd (suc j)  f i))

反向规格 select-in 说每个被标记的条目都被选中:位为 true 的原始位置 i 拥有一个被选位置 j,其条目为 f i。同样,这一主张是显式的数据,即一个真实的 j 连同一条路径。两个方向都不带截断,这正是后文关于成员性的论证能在选取两侧传递真实见证的原因。

  step (i , e , q) = suc i , (e , q)

select-in : {ℓ' : Level} {X : Type ℓ'} (n : ) (f : Fin n  X) (v : Vec Bool n)
            (i : Fin n)  lookup i v  true
           Σ[ j  Fin (select n f v .fst) ] (select n f v .snd j  f i)
select-in zero    f []          ()      e

其证明从另一端映照同一递归。空族中的位置是荒谬的。false 情形中头部不可能被标为真,故假设 efalse≢true 矛盾;右移后的位置照旧递归。true 情形中头部以零号位置作答,更深的位置照旧递归。

select-in (suc n) f (false  v) zero    e = Empty.rec (false≢true e)
select-in (suc n) f (false  v) (suc i) e = select-in n  i  f (suc i)) v i e
select-in (suc n) f (true  v)  zero    e = zero , refl
select-in (suc n) f (true  v)  (suc i) e = step (select-in n  i  f (suc i)) v i e)
  where

最后一条子句完成前置的簿记:尾部找到的位置变成现在头部在前的新族中的 suc j,条目等式原样保留。两条规格合起来说明选取结果既不比掩码标出的多、也不比它少,尽管没有断言这两种位置对应方式互为逆映射。

  step : Σ[ j  Fin (select n  i  f (suc i)) v .fst) ]
           (select n  i  f (suc i)) v .snd j  f (suc i))
        Σ[ j  Fin (select (suc n) f (true  v) .fst) ]
           (select (suc n) f (true  v) .snd j  f (suc i))
  step (j , q) = suc j , q

marks 把筛选反过来用:它不读掩码来保留条目,而是取一个关于条目的布尔裁决 d,并写下记录该裁决的掩码,每个位置一位。基情形是空向量,递归步在头部询问 d 并沿右移后的族继续。

marks : {ℓ' : Level} {X : Type ℓ'} (n : )  (Fin n  X)  (X  Bool)  Vec Bool n
marks zero    f d = []
marks (suc n) f d = d (f zero)  marks n  i  f (suc i)) d

marks-lookup : {ℓ' : Level} {X : Type ℓ'} (n : ) (f : Fin n  X) (d : X  Bool)
               (i : Fin n)  lookup i (marks n f d)  d (f i)

marks-lookup 证明记录下的掩码确实在每个位置回答裁决:在 marks n f d 的位置 i 处查询得到 d (f i)。头部情形由 marks 的计算规则得 refl,更深的位置照旧递归。有了这条引理,后面的 maskOf 才能证明它写下的掩码重现给定的子集。

marks-lookup (suc n) f d zero    = refl
marks-lookup (suc n) f d (suc i) = marks-lookup n  i  f (suc i)) d i

把一个真值判定成一位

排中律把每个命题化为掩码所用的布尔位,而两条规格从该位分别读回真与假。

排中律给出的是一个析取,而掩码需要的是一位,故须把二者衔接起来。裁决作为实参显式传入,而不是在定义内部求解:正是这一点使两条来回引理能靠对它作模式匹配来证明;真值本身也显式给出,使来回规格以预期命题为参数。

这一转换是排中律在点名册构造中的一个具体用途:判定一条成员命题,再把答案记录为一位。

decideOf 把裁决变成一位:左支是 ⟨ P ⟩ 的证明,记为 true;右支是 ⟨ P ⟩ 的反驳,记为 false。命题 P 本身与计算无关,被匹配的只是裁决,因此这个定义是一对方程而非证明。

decideOf : (P : hProp (ℓ-suc ))  ( P   ( P   Empty.⊥))  Bool
decideOf P (inl _) = true
decideOf P (inr _) = false

decide-true : (P : hProp (ℓ-suc )) (s :  P   ( P   Empty.⊥))   P   decideOf P s  true
decide-true P (inl _)  p = refl

两条往返把位接回真值。decide-true⟨ P ⟩ 的证明迫使该位为 true:在反驳支中这个证明本身会被反驳,那正是矛盾。decide-sound 反向读出:位为 true 便给出 ⟨ P ⟩ 的证明,或直接取自左支,或因右支会迫使 false ≡ true 而得。合起来,它们说明对于传入的那个裁决,该位忠实地回答 ⟨ P ⟩ 是否成立。

decide-true P (inr np) p = Empty.rec (np p)

decide-sound : (P : hProp (ℓ-suc )) (s :  P   ( P   Empty.⊥))  decideOf P s  true   P 
decide-sound P (inl p) _ = p
decide-sound P (inr _) e = Empty.rec (false≢true e)

已清点层的可定义子集

有穷性经由本节沿塔逐级传递。固定序数 σ 和层 Lset σ 的一份点名册,目标是给出 𝒟ₒ (Lset σ) (该层可定义子集的全体) 的点名册。已知点名册的每个条目都是该层的成员,因而在该层的小成员类型中有相应的元素;掩码指明保留哪些元素,part 把保留的元素张成有穷集。按基本公理一章的 finSet∈𝒟ₒ,这样张成的集合是该层的可定义子集,由「等于这些条目之一」的有穷析取定义。反过来,该层的任何可定义子集 x 也能被恢复:按每个点名册条目是否属于 x 的可判定成员关系加以标记,该掩码张成的集合恰是 x,其中包含关系 𝒟ₒ∋⊆ 保证 x 的每个成员本就被点名册列出。于是 maskCount size 个掩码仅仅覆盖全部可定义子集,而这正是 Tally 所要求的。

Lset σ 的成员作为集合处在该层中,但 finSet 需要小成员类型 ⟪ Lset σ ⟫ 中的名字;嵌入 ⟪ Lset σ ⟫↪ 把这种名字读成集合。成员关系呈现为截断原像,不过这个嵌入的原像取值为命题,所以 ∈-asFiber 可以消去截断,返回一个显式名字及其等同于 item i路径index iindex-eq i 正是该原像元素的两个投影。这并非从任意点名册原像中选取索引,因为允许重复的点名册原像未必是命题。

module PowerStep (σ : S) ( : IsOrd σ) (t : Tally (Lset σ)) where
  open Tally t
  open FinOf σ  using ( finSet∈𝒟ₒ )

  index : Fin size   Lset σ 
  index i = ∈-asFiber {a = item i} {b = Lset σ} (inside i) .fst

同一纤维的第二个分量是路径 index-eq i,它记录嵌入元素经一条路径而非定义等式回到 item i。此后在集合 item i 与元素 index i 之间的每一次转换都要沿这条路径用传输完成。元素就位后,掩码 v 被转换为一次选取:chosen v 给出一个长度,连同恰好列出被选元素的函数,这由此前的 select 构造。

  index-eq : (i : Fin size)   Lset σ ⟫↪ (index i)  item i
  index-eq i = ∈-asFiber {a = item i} {b = Lset σ} (inside i) .snd

  chosen : Vec Bool size  Σ[ k   ] (Fin k   Lset σ )
  chosen v = select size index v

  part : Vec Bool size  S

part 就是张成的集合:它把每个被选元素经嵌入读出,并取所得结果的有穷集,落在集合类型 S 中。由于 Lset σ 成员的有穷族张成该层的可定义子集,part-def 直接由 finSet∈𝒟ₒ 得到证书 ⟨ part v ∈ˢ 𝒟ₒ (Lset σ) ⟩,无须额外工作。第一条规格从反方向读成员关系:若 y 属于 part v,则仅仅存在某个点名册位置,其位为 true 且其条目等于 y

  part v = finSet (chosen v .fst)  j   Lset σ ⟫↪ (chosen v .snd j))

  part-def : (v : Vec Bool size)   part v ∈ˢ 𝒟ₒ (Lset σ) 
  part-def v = finSet∈𝒟ₒ (chosen v .fst) (chosen v .snd)

  part-out : (v : Vec Bool size) (y : S)   y ∈ˢ part v 
             Σ[ i  Fin size ] ((lookup i v  true) × (item i  y)) ∥₁

证明分两步复合。第一步,finSet-out 解开有穷张成集中的成员关系:它仅仅给出选取中的一个位置 j,使嵌入元素等于 y。第二步,select-out 把该位置追回到完整点名册中的来源,恢复索引 i,满足 lookup i v ≡ true 以及 chosen v .snd j ≡ index i。两步的数据都在截断之内产生,因此没有从单纯存在性命题中提取选定的见证。

  part-out v y y∈ = PT.map step
    (finSet-out (chosen v .fst)  j   Lset σ ⟫↪ (chosen v .snd j)) y y∈)
    where
    step : Σ[ j  Fin (chosen v .fst) ] ( Lset σ ⟫↪ (chosen v .snd j)  y)
          Σ[ i  Fin size ] ((lookup i v  true) × (item i  y))

最后所需等式的方向是 item i ≡ y。先沿 sym (index-eq i)item i 到嵌入后的名字 index i。随后 select-out 给出 chosen v .snd j ≡ index i,取其对称并施加嵌入,便到达选中的嵌入名字。最后,有限集成员关系给出的路径 q 到达 y。三者的复合正是证明中显示的三段路径

    step (j , q) = out .fst
                 , ( out .snd .fst
                   , (sym (index-eq (out .fst))
                       cong  Lset σ ⟫↪ (sym (out .snd .snd))  q) )
      where

相反的规格正向运行。若位置 i 处的位为 true,则条目 item i 确实属于 part v。原因在于选取中确实含有该元素:select-in 对每个被标记的位置,在被选族中找到一个持有同一元素的槽位,随后 finSet-in 证明其嵌入形式的成员关系。

      out : Σ[ i  Fin size ] ((lookup i v  true) × (chosen v .snd j  index i))
      out = select-out size index v j

  part-mem : (v : Vec Bool size) (i : Fin size)  lookup i v  true
             item i ∈ˢ part v 
  part-mem v i e = subst  w   w ∈ˢ part v ) path

由于张成集中的成员关系是针对嵌入元素陈述的,而目标针对条目 item i,两者要靠下文的路径 path 连接,并用 subst 沿该路径搬移成员证书。辅助的 ins 保存 select-in 给出的槽位:被选族中的一个位置,其条目等于 index i

    (finSet-in (chosen v .fst)  j   Lset σ ⟫↪ (chosen v .snd j))
      ( Lset σ ⟫↪ (chosen v .snd (ins .fst)))  ins .fst , refl ∣₁)
    where
    ins : Σ[ j  Fin (chosen v .fst) ] (chosen v .snd j  index i)
    ins = select-in size index v i e

余下的路径 path 把槽位的等式与 index-eq i 拼接,因此传输后的成员关系正是 item i 的成员关系。两个方向就位后,构造可以反向运行。maskOf 对任意集合 x 给出裁决掩码:对每个点名册条目判定它是否属于 x;排中律 lem 供给析取,decideOf 把它变成一位。目标 part-mask 陈述:对该层中的可定义子集 x,此掩码张成的集合就是 x 本身。

    path :  Lset σ ⟫↪ (chosen v .snd (ins .fst))  item i
    path = cong  Lset σ ⟫↪ (ins .snd)  index-eq i

  maskOf : S  Vec Bool size
  maskOf x = marks size item  y  decideOf (y ∈ˢ x) (lem (y ∈ˢ x)))

  part-mask : (x : S)   x ∈ˢ 𝒟ₒ (Lset σ)   part (maskOf x)  x

层次中集合的成员关系是命题,因此外延性 extensionalV 把所断言的等式 part (maskOf x) ≡ x 归约为逐点的成员关系等价;⇔toPath 把两个方向组装成路径。正向表明张成集的每个成员都属于 x

  part-mask x x∈ = extensionalV  y  ⇔toPath (fwd y) (bwd y))
    where
    fwd : (y : S)   y ∈ˢ part (maskOf x)    y ∈ˢ x 
    fwd y y∈ = PT.rec (snd (y ∈ˢ x)) step (part-out (maskOf x) y y∈)
      where

正向的前提本身就是单纯的存在性:某个被标记的位置,其条目等于 y。由于目标 ⟨ y ∈ˢ x ⟩ 是命题,截断可以消去到其中。记录的见证是位置 i,其位为 true 且条目为 y;由于该位正是通过判定这个条目是否属于 x 算出的,用 decide-sound 把位读回即得 item i 属于 x,再用等式 item i ≡ y 把它传输给 y

      step : Σ[ i  Fin size ] ((lookup i (maskOf x)  true) × (item i  y))
             y ∈ˢ x 
      step (i , e , q) = subst  w   w ∈ˢ x ) q
        (decide-sound (item i ∈ˢ x) (lem (item i ∈ˢ x))
          (sym (marks-lookup size item

反向从 y 属于 x 出发,须产生张成集中的成员关系。由于该目标又是命题,其截断的前提可以消去。这里的前提来自点名册的覆盖:x 是该层的可定义子集,而 𝒟ₒ∋⊆Lset σ 的可定义子集的每个成员都是 Lset σ 自身的成员,因此点名册的 onto 单纯地把 y 列为某个条目 item i

                  z  decideOf (z ∈ˢ x) (lem (z ∈ˢ x))) i)  e))
    bwd : (y : S)   y ∈ˢ x    y ∈ˢ part (maskOf x) 
    bwd y y∈x = PT.rec (snd (y ∈ˢ part (maskOf x))) step
      (onto y (𝒟ₒ∋⊆ (Lset σ) x x∈ y y∈x))
      where

给定等于 y 的条目 i,只需证明 item i 属于张成集,并沿 item i ≡ y 传输。由 part-mem,成员关系需要位置 i 的位为 true。它确实如此:掩码记录了 item i ∈ˢ x 的判定,而由于 y 属于 x路径 item i ≡ y 把该证明传输过来,decide-true 便迫使该位为 true

      step : Σ[ i  Fin size ] (item i  y)   y ∈ˢ part (maskOf x) 
      step (i , q) = subst  w   w ∈ˢ part (maskOf x) ) q
        (part-mem (maskOf x) i
          (marks-lookup size item  z  decideOf (z ∈ˢ x) (lem (z ∈ˢ x))) i
            decide-true (item i ∈ˢ x) (lem (item i ∈ˢ x))

part-mask 的两个方向就此组装完毕,本节的关键成果随之而来。由于每个掩码都经 mask-onto 来自某个索引,掩码 (允许重复、单纯地) 枚举了 Lset σ 的全部可定义子集。它们共有 maskCount size 个,因此 powerTally 记录一个该大小的点名册,其在索引 j 处的条目是掩码 maskAt size j 张成的集合。其余字段补全记录:每个条目附带其可定义性证书,覆盖条款随后给出。

               (subst  w   w ∈ˢ x ) (sym q) y∈x)))

  powerTally : Tally (𝒟ₒ (Lset σ))
  powerTally = record
    { size   = maskCount size
    ; item   = λ j  part (maskAt size j)

记录的 inside 字段在每个被枚举的掩码处复用证书 part-def,因此 powerTally 的每个条目确实是该层的可定义子集。剩下检查 onto,即截断的覆盖性。给定 Lset σ 的任意可定义子集 x,必须单纯地给出一个索引,其被枚举的条目等于 x

    ; inside = λ j  part-def (maskAt size j)
    ; onto   = cover }
    where
    cover : (x : S)   x ∈ˢ 𝒟ₒ (Lset σ) 
            Σ[ j  Fin (maskCount size) ] (part (maskAt size j)  x) ∥₁

见证索引是 mask-onto 为裁决掩码 maskOf x 产生的那个。该索引处被枚举的条目是 part (maskAt size j),沿所得路径改写掩码后它等于 part (maskOf x),随后 part-mask 把它与 x 等同。整个命题落在截断之中,这正是 Tally 的覆盖性所要求的:每个可定义子集都被命中,尽管未必由唯一的掩码命中。

    cover x x∈ =  mask-onto size (maskOf x) .fst
                 , (cong part (mask-onto size (maskOf x) .snd)  part-mask x x∈) ∣₁

最小元与良基性

本节花用的是先前造好的点名册,而非再造一份。固定一个类型及其上一个三歧、非自反且传递的关系,即严格良序所要求的一切,只差良基。过程 scan 走过一个有穷族,并且不带任何截断地返回:要么是一个满足谓词、且在满足者之中最小的条目,要么是「没有条目满足它」的反驳。它是沿长度的普通递归:每一步由排中律判定谓词在头部是否成立,再由三歧比较头部与迄今为止的最佳者;四种组合即四条子句。全程无截断这一点很要紧,因为调用方要的是一个货真价实的元素,不是仅仅的存在性。在「该族单纯覆盖整个类型」的假设下,Search.Over.least 把它升级为「整个类型上任一单纯非空谓词的最小元」:「没有条目满足它」那一支被见证者驳倒,因为该谓词本该命中它在族中的纤维。良基性随后的最小反例论证在其代码处再作说明。

固定 A 上满足三歧、非自反与传递的严格关系 。目标是从有穷覆盖族推出良基性,而不是把良基性作为假设。对谓词 PLeast P m 同时记录 m 满足 P,以及每个严格更小的满足者都会导出矛盾。

module Search {A : Type (ℓ-suc )} (_≺_ : A  A  Type (ℓ-suc ))
              (tri : (a b : A)  Tri (a  b) (a  b) (b  a))
              (irr : (a : A)  a  a  Empty.⊥)
              (trans : (a b c : A)  a  b  b  c  a  c) where

  Least : (P : A  hProp (ℓ-suc ))  A  Type (ℓ-suc )

扫描的输出类型 Found P n f 是两个显式选项的析取。左支中,某个位置 i 持有一个满足 P 的条目,且族内没有其他满足 P 的条目位于其下。右支中,每个条目都不满足谓词。两个选项携带的都是完整数据而非截断的存在性,这使后续构造能返回真实的元素。

  Least P m =  P m  × ((b : A)   P b   b  m  Empty.⊥)

  Found : (P : A  hProp (ℓ-suc )) (n : ) (f : Fin n  A)  Type (ℓ-suc )
  Found P n f =
    (Σ[ i  Fin n ] ( P (f i)  × ((j : Fin n)   P (f j)   f j  f i  Empty.⊥)))
     ((i : Fin n)   P (f i)   Empty.⊥)

scan 沿族长度递归定义。空族空虚地返回右支。对有头部的族,递归先处理尾部,把位置整体后移一位;排中律对 P 在头部的裁决交给 combine,它把尾部的结果与头部的裁决合并成整个族的结果。

  scan : (P : A  hProp (ℓ-suc )) (n : ) (f : Fin n  A)  Found P n f
  scan P zero    f = inr  ())
  scan P (suc n) f = combine (scan P n  i  f (suc i))) (lem (P (f zero)))
    where
    combine : Found P n  i  f (suc i))

combine 的第一支处理尾部已经给出最小满足者 f (suc i)、而头部也满足谓词的情形。此时两个候选竞争,三歧判定 f zerof (suc i) 哪个更小;辅助函数 decide 分析该比较的三种结果。

             ( P (f zero)   ( P (f zero)   Empty.⊥))  Found P (suc n) f
    combine (inl (i , pi , mi)) (inl p₀) = decide (tri (f zero) (f (suc i)))
      where
      decide : Tri (f zero  f (suc i)) (f zero  f (suc i)) (f (suc i)  f zero)
              Found P (suc n) f

若头部严格小于尾部的优胜者,头部便成为新的优胜者。其最小性逐位置核验:在头部自身处,断言 f zero ≺ f zero 直接与非自反性矛盾;在尾部各位置,传递性把 f j ≺ f zero ≺ f (suc i) 连成链,交给尾部已确立的最小性 mi

      decide (lt h) = inl (zero , (p₀ , minAt))
        where
        minAt : (j : Fin (suc n))   P (f j)   f j  f zero  Empty.⊥
        minAt zero    pj hj = irr (f zero) hj
        minAt (suc j) pj hj = mi j pj (trans (f (suc j)) (f zero) (f (suc i)) hj h)

若头部等于尾部当前的最小候选,该候选仍为最小。假设头部低于候选,沿二者的等式传输后就得到候选低于自身,与非自反性矛盾;尾部位置仍由 mi 处理。

      decide (eq h) = inl (suc i , (pi , minAt))
        where
        minAt : (j : Fin (suc n))   P (f j)   f j  f (suc i)  Empty.⊥
        minAt zero    pj hj = irr (f (suc i)) (subst  w  w  f (suc i)) h hj)
        minAt (suc j) pj hj = mi j pj hj

若尾部的优胜者严格小于头部,它得以保留。此时优胜者之下的假设性条目有两条出路:经头部 f (suc i) ≺ f zero ≺ f (suc i) 的传递性给出一个自比较,由非自反性驳倒;而尾部自身的各位置交给 mi。优胜者的证书在每个分支都由旧证书重建。

      decide (gt h) = inl (suc i , (pi , minAt))
        where
        minAt : (j : Fin (suc n))   P (f j)   f j  f (suc i)  Empty.⊥
        minAt zero    pj hj = irr (f (suc i)) (trans (f (suc i)) (f zero) (f (suc i)) h hj)
        minAt (suc j) pj hj = mi j pj hj

第二支在头部不满足谓词时保留尾部的优胜者。这里完全不需要比较:头部既然不满足 P,便无从挑战优胜者,因此头部处的假想反例直接由裁决 n₀ 驳倒,尾部各位置依旧交给 mi

    combine (inl (i , pi , mi)) (inr n₀) = inl (suc i , (pi , minAt))
      where
      minAt : (j : Fin (suc n))   P (f j)   f j  f (suc i)  Empty.⊥
      minAt zero    pj hj = Empty.rec (n₀ pj)
      minAt (suc j) pj hj = mi j pj hj

对称地,当尾部全无满足者而头部确实满足谓词时,头部就是新的优胜者。其最小性立即可得:头部自身由非自反性处理,任何满足谓词的尾部位置都与尾部的反驳 none 矛盾。

    combine (inr none) (inl p₀) = inl (zero , (p₀ , minAt))
      where
      minAt : (j : Fin (suc n))   P (f j)   f j  f zero  Empty.⊥
      minAt zero    pj hj = irr (f zero) hj
      minAt (suc j) pj hj = Empty.rec (none j pj)

最后一支是一致情形:尾部与头部都给不出满足者,于是报告整个族中无人满足。反驳逐位置组装:头部交给 n₀,每个尾部位置交给 none。至此,导言所说的四种组合齐备。

    combine (inr none) (inr n₀) = inr atAll
      where
      atAll : (i : Fin (suc n))   P (f i)   Empty.⊥
      atAll zero    p = n₀ p
      atAll (suc i) p = none i p

子模块 Over 添加了把有穷族变成点名册所需的那条前提:covA 的每个元素都被该族单纯命中,这是允许重复的截断覆盖。在此前提下,least 把扫描的答案升级为整个类型上的最小元:其输入只是一个「某元素满足 P」的截断见证,其输出则是显式数据,即一个元素连同 Least P m

  module Over (n : ) (f : Fin n  A)
              (cov : (a : A)   Σ[ i  Fin n ] (f i  a) ∥₁) where

    least : (P : A  hProp (ℓ-suc ))   Σ[ a  A ]  P a  ∥₁  Σ[ m  A ] Least P m
    least P h = decide (scan P n f)
      where

least 内部,辅助函数 nowhere 处理扫描的「无满足者」分支:假定没有条目满足 P,就必须驳倒给定的截断见证。该消去是合法的,因为目标是空类型这一命题,因此见证的截断可以在不作任何选择的情况下拆开。

      nowhere : ((i : Fin n)   P (f i)   Empty.⊥)  Empty.⊥
      nowhere none = PT.rec Empty.isProp⊥ atWitness h
        where
        atWitness : Σ[ a  A ]  P a   Empty.⊥
        atWitness (a , pa) = PT.rec Empty.isProp⊥

具体而言,见证给出元素 a⟨ P a ⟩,覆盖 cov a 单纯地指出族中位置 i 满足 f i ≡ a;由于目标仍是命题,该纤维可以被读出。把 ⟨ P a ⟩ 的证明沿 f i ≡ a 反向传输得到 ⟨ P (f i) ⟩,假定的反驳 none 便将其化为矛盾。紧接的代码行执行的正是这次传输。

           { (i , q)  none i (subst  w   P w ) (sym q) pa) }) (cov a)
      decide : Found P n f  Σ[ m  A ] Least P m
      decide (inl (i , pi , mi)) = f i , (pi , everywhere)
        where
        everywhere : (b : A)   P b   b  f i  Empty.⊥

前面预告的传输在这里执行,且两个分量同时进行。给定整个类型中位于优胜者之下的假想条目 b,附有 ⟨ P b ⟩b ≺ f i,覆盖单纯地给出满足 f j ≡ b 的族位置 j;把满足性与比较性都沿该路径反向传输,优胜者的族级证书 mi 便把二者一并驳倒。于是扫描仅剩的分支,即反驳 none,彻底矛盾,因为见证已被证明必然把一个满足者带进族中。

        everywhere b pb hb = PT.rec Empty.isProp⊥
           { (j , q)  mi j (subst  w   P w ) (sym q) pb)
                              (subst  w  w  f i) (sym q) hb) }) (cov b)
      decide (inr none) = Empty.rec (nowhere none)

    wellFounded : WellFounded _≺_

为证明良基性,先判定任意 a 是否可及;肯定支直接返回证书。否定支用有穷扫描找出可及性被反驳的最小元素 m。若 m 的每个前驱都可及,acc below 就证明 m 可及;把 found 中保存的 m 的反驳作用于这份证书即得矛盾。最初关于 a 的反驳只用于证明「不可及」这一谓词非空。

    wellFounded a = fromDec (lem (Acc _≺_ a , isPropAcc a))
      where
      fromDec : (Acc _≺_ a  (Acc _≺_ a  Empty.⊥))  Acc _≺_ a
      fromDec (inl h) = h
      fromDec (inr nh) = Empty.rec (found .snd .fst (acc below))

被取最小的性质是 NotAcc,即不可及性。其底层陈述是一个否定,而否定是命题,故 NotAcc 是合法的真值 Ωleast 可以作用于它。输入是 a 与假定反驳 nh 的截断配对,因此该假设只是说不可及元素之集非空。

        where
        NotAcc : A  hProp (ℓ-suc )
        NotAcc b = (Acc _≺_ b  Empty.⊥) , isProp¬ _
        found : Σ[ m  A ] Least NotAcc m
        found = least NotAcc  a , nh ∣₁

m 是刚求得的最小不可及元素。要证它可及,须证每个前驱 b 可及,而 b 的可及性又是命题,故再次由排中律判定;辅助函数 pick 在肯定支中返回证书

        below : (b : A)  b  found .fst  Acc _≺_ b
        below b hb = pick (lem (Acc _≺_ b , isPropAcc b))
          where
          pick : (Acc _≺_ b  (Acc _≺_ b  Empty.⊥))  Acc _≺_ b
          pick (inl h)  = h

在否定支中,b 将是严格小于最小不可及元素 m 的不可及元素,而 Least NotAcc m 的最小性条款恰好驳斥这一点。于是每个前驱皆可及,证书 acc below 合法,把它交给假定的可及性反驳便封闭了矛盾。注意:全程并未构造或排除任何无穷下降序列,论证完全就是这个矛盾。

          pick (inr nb) = Empty.rec (found .snd .snd b nb hb)

最先的分歧

本节定义有穷层将要携带的序。固定一个集合 A 与集合之上的一个关系 R,后者读作 A 的诸成员上的一个序。A 的两个子集,按它们在何处分歧来比较。「x 先于 y」的见证,是 A 的一个成员 z,它属于 y 而不属于 x,且 xyz 之下一致,意即 A 中被 R 排在 z 之前的每个成员,属于其中之一当且仅当属于另一个。倒过来读:z 就是最先的分歧点,而它属于 y。关系 precedes R A 是这类见证的截断存在;非自反性立刻成立,且完全不需要任何前提:x 对自己的见证会既属于 x 又不属于 x。后文证明在关于基底序的前提下得到三歧与传递,并用有穷性得到良基性。

两个成分分别陈述。Agrees R A x y z 说:对 A 中被 R 排在 z 之前的每个成员 w,属于 x 与属于 y 双向重合。Witness R A x y z 随后组装完整见证:z 属于 A,属于 y,不属于 x,且其下方一致成立。正是成员条款的方向决定了比较中哪一方胜出。

Agrees : (R : S  S  hProp (ℓ-suc )) (A x y z : S)  Type (ℓ-suc )
Agrees R A x y z = (w : S)   w ∈ˢ A    R w z 
                  ( w ∈ˢ x    w ∈ˢ y ) × ( w ∈ˢ y    w ∈ˢ x )

Witness : (R : S  S  hProp (ℓ-suc )) (A x y z : S)  Type (ℓ-suc )
Witness R A x y z =

precedes R A x y 是「这类见证单纯存在」的命题,随 PT.squash₁ 打包成一个真值。由于见证藏在截断之后,被断言的只有其存在,任何东西都不选定 z。非自反性于是只花一行:把截断消去到空类型 (一个命题) 中,暴露出同时有 z ∈ xz ∉ x 的见证,把第二条施于第一条即是矛盾。

   z ∈ˢ A  ×  z ∈ˢ y  × ( z ∈ˢ x   Empty.⊥) × Agrees R A x y z

precedes : (R : S  S  hProp (ℓ-suc )) (A : S)  S  S  hProp (ℓ-suc )
precedes R A x y =  Σ[ z  S ] Witness R A x y z ∥₁ , PT.squash₁

precedes-irrefl : (R : S  S  hProp (ℓ-suc )) (A x : S)   precedes R A x x   Empty.⊥
precedes-irrefl R A x = PT.rec Empty.isProp⊥  { (z , _ , z∈ , z∉ , _)  z∉ z∈ })

最先分歧序的传递性与三歧确实需要关于基底序的前提,而二者所需不同,故一并收进一个模块。其参数是 RA 诸成员上的三歧与传递,以及 R 在那些成员上的最小元原则;在塔中,这些都来自下面那一层。

传递性是两个见证之间的比较。若 xp 处先于 yyq 处先于 z,则 pq 不可能相等,因为 p 属于 yq 不属于;而二者中较小的那个就见证了 x 先于 z。两支要核对的是同样的两件事:较小的那一点方向正确,以及它之下的一致性可以复合。

该模块收集最先分歧序将要继承的三条前提。baseTribaseTransR 限制在 A 的成员上时三歧且传递,baseLeastA 上的最小元原则:从「A 成员的某个性质单纯非空」出发,它给出一个满足该性质、且没有更小的 A 成员也满足的元素。注意结论的形状:它是显式数据而非截断,因为调用方需要真实的极小元。

module Difference (R : S  S  hProp (ℓ-suc )) (A : S)
  (baseTri : (a b : S)   a ∈ˢ A    b ∈ˢ A   Tri  R a b  (a  b)  R b a )
  (baseTrans : (a b c : S)   R a b    R b c    R a c )
  (baseLeast : (P : S  hProp (ℓ-suc ))   Σ[ a  S ] ( a ∈ˢ A  ×  P a ) ∥₁
              Σ[ m  S ] ( m ∈ˢ A  ×  P m 

传递性的陈述恰好按 precedes 的产出形式取两条前提:x ≺ yy ≺ z 的截断见证,并返回 x ≺ z 的截断见证。因此证明先消去第一个截断,再消去第二个,二者的目标都又是截断、因而是命题。

                 × ((b : S)   b ∈ˢ A    P b    R b m   Empty.⊥)))
  where

  precedes-trans : (x y z : S)   precedes R A x y    precedes R A y z 
                   precedes R A x z 
  precedes-trans x y z hxy hyz =

两个见证都暴露后,both 接收完整数据:见证 x 先于 y 的点 p 及其成员条款 agp,以及见证 y 先于 z 的点 q 及其 agq。两个基底点的比较交给基底三歧,辅助函数 decide 分析其三种结果。

    PT.rec PT.squash₁  wp  PT.rec PT.squash₁ (both wp) hyz) hxy
    where
    both : Σ[ p  S ] Witness R A x y p  Σ[ q  S ] Witness R A y z q
           precedes R A x z 
    both (p , p∈A , p∈y , p∉x , agp) (q , q∈A , q∈z , q∉y , agq) =

p 严格小于 q,它继续充当 x 先于 z 的见证。它自身的条款原封不动,因为它们只涉及 xy;须核实的是 p 属于 z,以及 p 之下 xz 的一致性。p 属于 zagq 在点 p 处给出,它把 py 的成员关系沿复合比较传输过去。

      decide (baseTri p q p∈A q∈A)
      where
      decide : Tri  R p q  (p  q)  R q p    precedes R A x z 
      decide (lt h) =  p , (p∈A , (agq p p∈A h .fst p∈y , (p∉x , ag))) ∣₁
        where

p 之下的一致性逐条款复合。要证 w ∈ x 蕴含 w ∈ zagpw ∈ x 提升为 w ∈ y,再用 agq 把对 y 的成员提升到 z,其中用基底传递性保证 w 也位于 q 之下。反向条款对称,把 z 降到 y 再降到 x。相等情形不可能出现:p 属于 yq 不属于,沿路径 p ≡ q 传输成员关系即得矛盾。

        ag : Agrees R A x z p
        ag w w∈A hw =
             wx  agq w w∈A (baseTrans w p q hw h) .fst (agp w w∈A hw .fst wx))
          ,  wz  agp w w∈A hw .snd (agq w w∈A (baseTrans w p q hw h) .snd wz))
      decide (eq h) = Empty.rec (q∉y (subst  v   v ∈ˢ y ) h p∈y))

若改为 q 严格小于 p,角色对调:由 q 见证 x 先于 z。它关于 yz 的条款照旧,但须确立对 x 的成员与一致性。关于成员,在点 q 处读 agp,把 qx 的成员传输为对 y 的成员,与 q ∉ y 矛盾;辅助函数 q∉x 把这一反驳打包。

      decide (gt h) =  q , (q∈A , (q∈z , (q∉x , ag))) ∣₁
        where
        q∉x :  q ∈ˢ x   Empty.⊥
        q∉x qx = q∉y (agp q q∈A h .fst qx)
        ag : Agrees R A x z q

q 之下的一致性以镜像顺序复合:先用 agp 借助 q ≺ p 的基底传递性把 w 置于 p 之下,从而把对 x 的成员下推到 yagq 再把它上提到 z;反向条款先把 z 降到 y,再降到 x。两个不对称情形都已处理、相等已被驳倒,传递性就此完成。

        ag w w∈A hw =
             wx  agq w w∈A hw .fst (agp w w∈A (baseTrans w q p hw h) .fst wx))
          ,  wz  agp w w∈A (baseTrans w q p hw h) .snd (agq w w∈A hw .snd wz))

三歧正是使用排中律与最小元原则的地方。先问这两个子集在 A 中是否有分歧之处。若没有,则它们在 A 中处处一致;又因二者都不超出 A,故它们本就处处一致,外延性把它们认同。若有,则存在一个最先的分歧点;再作一次判定,即该点是否属于第一个子集,就知道比较朝哪个方向走。该点之下的一致性在两支中都自动成立:按该点的选法,它之下无一处分歧。

排中律在 agree 内部第二次被使用,用来把「没有分歧」变成「一致」;这一步恰是一次双重否定的消去。

这里的陈述并不把 A 的两个子集 xy 当作可定义性证书,而是当作普通集合,并附上二者都不超出 A 的前提。结论是一个 Tri,即本章通用的三分判断:x 先于 y、作为集合相等,或 y 先于 x。证明先对 Some 使用排中律发问;Some 将被构造成一个命题,即一条截断的存在陈述,因此可以把 PT.squash₁ 作为其命题性证书交给 lem

  precedes-tri : (x y : S)  ((w : S)   w ∈ˢ x    w ∈ˢ A )
                            ((w : S)   w ∈ˢ y    w ∈ˢ A )
                Tri  precedes R A x y  (x  y)  precedes R A y x 
  precedes-tri x y x⊆ y⊆ = decide (lem (Some , PT.squash₁))
    where

两条截断组织了这个问题。谓词 Apart w 仅仅说 w 区分了这两个子集,方向不限:它属于其一而不属于另一。截断类型 Some 仅仅说 A 的某个成员是分歧点。二者都配以 PT.squash₁,因而都是命题而非数据;这正是可以用排中律判定它们、随后又能把 Some 的反驳消去成矛盾的依据。

    Apart : S  hProp (ℓ-suc )
    Apart w =  ( w ∈ˢ x  × ( w ∈ˢ y   Empty.⊥))
               (( w ∈ˢ x   Empty.⊥) ×  w ∈ˢ y ) ∥₁ , PT.squash₁
    Some : Type (ℓ-suc )
    Some =  Σ[ a  S ] ( a ∈ˢ A  ×  Apart a ) ∥₁

辅助引理 agree 把「无分歧」转成「一致」,一次一个方向。前提 na 反驳 Apart w,结论是 w 处成员等价的两条包含子句。证明只有这里需要从否定性陈述造出成员蕴含,而它实际上是化了装的双重否定消去。

    agree : (w : S)  ( Apart w   Empty.⊥)
           ( w ∈ˢ x    w ∈ˢ y ) × ( w ∈ˢ y    w ∈ˢ x )
    agree w na = fwd , bwd
      where
      fwd :  w ∈ˢ x    w ∈ˢ y 

前向子句:设 w ∈ˢ x,对 w ∈ˢ y 用排中律发问。若成立即完成。若得到反驳 nh,那么 w 其实是分歧点,左析取支 wx , nh 就是见证;把该见证装入截断交给 na 便得矛盾,Empty.rec 再从矛盾产出所需元素,这里就是缺失的成员证明。目标 Empty.⊥ 是命题,故把截断的 Apart w 消去到它是正当的。

      fwd wx = pick (lem (w ∈ˢ y))
        where
        pick : ( w ∈ˢ y   ( w ∈ˢ y   Empty.⊥))   w ∈ˢ y 
        pick (inl h)  = h
        pick (inr nh) = Empty.rec (na  inl (wx , nh) ∣₁)

后向子句是其镜像。设 w ∈ˢ y,排中律判定 w ∈ˢ x;若有反驳,则经右析取支 nh , wy 会使 w 成为分歧点,而 na 恰好反驳这一点。两条子句合起来说:若在 w 处不存在差异点,则属于 x 与属于 yw 处重合。

      bwd :  w ∈ˢ y    w ∈ˢ x 
      bwd wy = pick (lem (w ∈ˢ x))
        where
        pick : ( w ∈ˢ x   ( w ∈ˢ x   Empty.⊥))   w ∈ˢ x 
        pick (inl h)  = h

现在设 Some 被反驳,即 A 中没有分歧点。辅助引理 nApart 把这一点包装成对 Apart 的逐点反驳,same 将在每处 w 使用它来证明两集合相等。被反驳的见证落在 A 中这一前提在下一步了结,随后 agree 的成员等价即可在每点使用。

        pick (inr nh) = Empty.rec (na  inr (nh , wy) ∣₁)
    same : (Some  Empty.⊥)  x  y
    same ns = extensionalV step
      where
      nApart : (w : S)   Apart w   Empty.⊥

只要两个子集都落在 A 中,分歧点必属于 A。事实上,截断析取 ha 被消去到命题 w ∈ˢ A 中:左支成立时 w 属于 xx⊆ 把它送进 A;右支成立时 y⊆ 同理。注意消去的方向:进入取值为命题的成员关系,这恰是命题截断所允许的。

      nApart w ha = ns  w , (inA , ha) ∣₁
        where
        inA :  w ∈ˢ A 
        inA = PT.rec (snd (w ∈ˢ A))
           { (inl (wx , _))  x⊆ w wx ; (inr (_ , wy))  y⊆ w wy }) ha

在每处 wagree w (nApart w) 的两条子句断言:属于 x 当且仅当属于 y。组合子 ⇔toPath 把这两个命题 w ∈ˢ xw ∈ˢ y 之间的这份当且仅当提升为二者作为类型之间的路径,而这正是累积层级的外延性所消费的形式。把逐点路径交给 extensionalV 便得路径 x ≡ y,于是三歧的 eq 分支得证。

      step : (w : S)  (w ∈ˢ x)  (w ∈ˢ y)
      step w = ⇔toPath (agree w (nApart w) .fst) (agree w (nApart w) .snd)
    decide : (Some  (Some  Empty.⊥))
            Tri  precedes R A x y  (x  y)  precedes R A y x 
    decide (inr ns) = eq (same ns)

另一支中 Some 成立:A 的某个成员是分歧点。把最小元原则 baseLeast (对 A 的成员上的基底序 R 可用) 作用于谓词 Apart,它返回显式的记录 found,而非截断的存在陈述:一个点 m,在 A 中、分歧,且在 R 序之下其下方再无分歧点。正是这种显式性,使得最小分歧点此后能被用作见证。

    decide (inl hs) = side (lem (m ∈ˢ x))
      where
      found : Σ[ m  S ] ( m ∈ˢ A  ×  Apart m 
                × ((b : S)   b ∈ˢ A    Apart b    R b m   Empty.⊥))
      found = baseLeast Apart hs

found 的各分量被一次性拆开并命名:点 m、其在 A 中的成员关系 m∈A、分歧性 apartM、以及最小性 belowM。逐一命名使下面两个对称分支保持可读,因为每一支都要引用其中若干字段

      m : S
      m = found .fst
      m∈A :  m ∈ˢ A 
      m∈A = found .snd .fst
      apartM :  Apart m 

最小性字段 belowM 反驳任何严格低于 m 的分歧点;这里把它改排为比较假设在末位的形式,以配合即将到来的用法。手握最小分歧点之后,排中律判定 m 是否属于 xside 把每个答案化为三歧的一个分支。

      apartM = found .snd .snd .fst
      belowM : (w : S)   w ∈ˢ A    R w m    Apart w   Empty.⊥
      belowM w w∈A hw ha = found .snd .snd .snd w w∈A ha hw
      side : ( m ∈ˢ x   ( m ∈ˢ x   Empty.⊥))
            Tri  precedes R A x y  (x  y)  precedes R A y x 

m 确实属于 x,则 m 见证 y 先于 x:它在第二个集合中而不在第一个中。子引理 m∉y 通过对截断的 apartM 作情形分析来反驳 m ∈ˢ y:左支中见证本身就带有对 m ∈ˢ y 的反驳;右支中对 m ∈ˢ x 的反驳与 mx 相抵触。消去截断是允许的,因为目标 Empty.⊥ 是命题。

      side (inl mx) = gt  m , (m∈A , (mx , (m∉y , ag))) ∣₁
        where
        m∉y :  m ∈ˢ y   Empty.⊥
        m∉y my = PT.rec Empty.isProp⊥
           { (inl (_ , nmy))  nmy my ; (inr (nmx , _))  nmx mx }) apartM

m 之下的一致性也免费换边。对 m 之下的每个 wbelowM 反驳 Apart w,故 agree w 适用,给出双向的成员等价;这里只是把二元组按相反次序写出,把原本从 xy 取向的一致性变成 Agrees R A y x m。与 m∈Amxm∉y 合起来,这是一份完整的 Witness,见证 y 先于 x,由 gt 装入截断交付。

        ag : Agrees R A y x m
        ag w w∈A hw = agree w (belowM w w∈A hw) .snd , agree w (belowM w w∈A hw) .fst
      side (inr nmx) = lt  m , (m∈A , (my , (nmx , ag))) ∣₁
        where
        my :  m ∈ˢ y 

镜像的一支改设 m 不属于 x,产出 x 先于 ylt 见证。从 apartM 提取 m ∈ˢ y 又是一次截断情形分析:左支会断言 m ∈ˢ x,被 nmx 反驳,故只有右支存活,而它直接带有该成员关系。这次 m 之下的一致性无须换向,因为见证的取向恰与 agree 的产出一致。两个对称分支齐备后,precedes 的三歧完成,一层上的局部序便是其成员上的线序,只待良基性。

        my = PT.rec (snd (m ∈ˢ y))
           { (inl (mx , _))  Empty.rec (nmx mx) ; (inr (_ , h))  h }) apartM
        ag : Agrees R A x y m
        ag w w∈A hw = agree w (belowM w w∈A hw)

有穷诸层

沿数码的递归把点名册与最先分歧良序从每个有穷层传到下一层。

以数码为索引的层正是有穷层,每层上的序由递归构造:第零层为空;n 的后继层上的序以层 n 自身的序为基础,并按最先分歧处比较层 n 的可定义子集。before-irrefl 在每层都成立且无需归纳,因为该比较的非自反性不需要前提,而第零层没有任何比较。

定义从三分判断的一件小工具开始。Tri-mapTri 逐支作用:每个备选支各应用一个函数;三条子句就是它的计算规则。它将把「关于两个集合证明的三歧」转换为「关于一层的两个点所需的三歧」,二者只差是否附带成员证明。

Tri-map : {ℓ₁ ℓ₂ ℓ₃ ℓ₄ ℓ₅ ℓ₆ : Level}
          {A₁ : Type ℓ₁} {B₁ : Type ℓ₂} {C₁ : Type ℓ₃}
          {A₂ : Type ℓ₄} {B₂ : Type ℓ₅} {C₂ : Type ℓ₆}
         (A₁  A₂)  (B₁  B₂)  (C₁  C₂)  Tri A₁ B₁ C₁  Tri A₂ B₂ C₂
Tri-map f g h (lt a) = lt (f a)

以数码为索引的层在此命名:finiteStage n 即层 Lset (# n)。关系 before 随后是对索引的递归。零处它取假真值,任何一对都不会被关系到。后继处它是对前一层使用 precedes:比较隶属关系的基底集合就是层 n 本身,而寻找最先分歧所沿的基底序是 before n,即递归在下一层造出的那个序。

Tri-map f g h (eq b) = eq (g b)
Tri-map f g h (gt c) = gt (h c)

finiteStage :   S
finiteStage n = Lset (# n)

before :   S  S  hProp (ℓ-suc )

before 的非自反性在所有数码处成立,且证明不作归纳。零处前提是假命题的证明,由 Empty.rec* 消去;后继处恰是 precedes-irrefl,即定义该比较时已确立的无前提非自反性。正因如此,非自反性不属于递归必须携带的数据。

before zero    x y = 
before (suc n) = precedes (before n) (finiteStage n)

before-irrefl : (n : ) (x : S)   before n x x   Empty.⊥
before-irrefl zero    x h = Empty.rec* h
before-irrefl (suc n) x h = precedes-irrefl (before n) (finiteStage n) x h

基例的空性单独记录为 zero-empty:没有集合是第零层的成员。从 Lset (# zero) 读出成员证书,仅仅给出某一层 δ,使 δ 属于数码零且 xLset δ 的可定义子集;数码零没有成员,∅-empty 把任何所谓的成员变成矛盾。由于目标 Empty.⊥ 是命题,消去该截断是正当的。

zero-empty : (x : S)   x ∈ˢ finiteStage zero   Empty.⊥
zero-empty x h = PT.rec Empty.isProp⊥ step (Lset-out (# zero) x h)
  where
  step : Σ[ δ  S ] ( δ ∈ˢ   ×  x ∈ˢ 𝒟ₒ (Lset δ) )  Empty.⊥
  step (δ , δ∈ , _) = ∅-empty δ (∈∈ₛ {a = δ} {b = } .fst δ∈)

递归必须携带的数据只有一份对成员的清点、三歧与传递:非自反性在每层都自动成立,而良基性只在用到之处当场推出,不必随身携带。层的一个点是一个集合连同它的隶属证明;由于隶属是命题,两个点只要集合相等就相等。在「关于集合的陈述」与「载体必须是类型的那个束」之间往返时,需要做的全部工作就在于此。

前节的搜索机制作用在类型上,故层的一个成员被包装成 Point:一个集合连同它在 finiteStage n 中的成员证书。关系 Below 在底层集合处读取 before n。由于隶属是命题,集合相同的两个点已然相等;这一个事实承担了「关于集合的陈述」与「关于点的陈述」之间往返的全部工作。

Point :   Type (ℓ-suc )
Point n = Σ[ x  S ]  x ∈ˢ finiteStage n 

Below : (n : )  Point n  Point n  Type (ℓ-suc )
Below n a b =  before n (a .fst) (b .fst) 

record StageOrder (n : ) : Type (ℓ-suc ) where

n 的归纳恰好保留后继步所需的事实:finiteStage n 的点名册、before n 对该层成员的三歧性,以及 before n 对任意集合的传递性。非自反性由最先分歧统一推出;局部序需要良基性时,则从点名册重新得到。

  field
    tally : Tally (finiteStage n)
    tri   : (x y : S)   x ∈ˢ finiteStage n    y ∈ˢ finiteStage n 
           Tri  before n x y  (x  y)  before n y x 
    trans : (x y z : S)   before n x y    before n y z    before n x z 

Ordered 内部,第一项任务是关于点的三歧。triPoint 把关于集合的三歧 tri 交给 Tri-map;中间一支的结论是路径,需要转换,Σ≡Prop 恰好提供这一点:由于第二分量是某个命题的证明,底层集合之间的路径可以延拓为点之间的路径

module Ordered (n : ) (r : StageOrder n) where
  open StageOrder r public
  open Tally tally

  triPoint : (a b : Point n)  Tri (Below n a b) (a  b) (Below n b a)
  triPoint a b = Tri-map id (Σ≡Prop  z  snd (z ∈ˢ finiteStage n))) id

点名册由集合提升到点:把每个条目配上它自己的成员证明,得到 points。覆盖陈述 covers 随后是 onto 经这一配对的搬运:给定一个点,onto 仅仅提供一个索引,其条目具有相同的集合,Σ≡Prop 再把集合的等式升级为点的等式。覆盖仍然是截断的,与点名册本身一样。

    (tri (a .fst) (b .fst) (a .snd) (b .snd))

  points : Fin size  Point n
  points i = item i , inside i

  covers : (a : Point n)   Σ[ i  Fin size ] (points i  a) ∥₁
  covers a = PT.map  { (i , q)  i , Σ≡Prop  z  snd (z ∈ˢ finiteStage n)) q })

在层的点上,三歧、非自反与传递同有穷点名册结合,给出两个结论:有穷扫描为每个仅仅非空的谓词找出最小点,而同一个最小反例论证给出点关系的良基性。

    (onto (a .fst) (a .snd))

  open Search (Below n) triPoint  a  before-irrefl n (a .fst))
               a b c  trans (a .fst) (b .fst) (c .fst)) public
  open Over size points covers public

  order : SWO (Point n)

这些事实确定了 finiteStage n 诸点上的严格良序:关系是 Before n,三条序律来自层比较,良基性则来自有穷扫描。因此,构造清楚地区分了局部比较与排除无穷下降的有穷性论证。

  order = record
    { _<∙_   = Below n
    ; tri∙   = triPoint
    ; irr∙   = λ a  before-irrefl n (a .fst)
    ; trans∙ = λ a b c  trans (a .fst) (b .fst) (c .fst)

最后一条引理以下一层所需的形状包装最小元。leastMem 取集合上的一个谓词 P,它仅仅被该层的某个成员满足,并返回显式的满足 P 的成员 m,连同 before n 序下的最小性:该层中满足 P 的成员 b 没有严格低于 m 的。除前提外,这里没有任何截断。

    ; wf∙    = wellFounded }

  leastMem : (P : S  hProp (ℓ-suc ))   Σ[ a  S ] ( a ∈ˢ finiteStage n  ×  P a ) ∥₁
            Σ[ m  S ] ( m ∈ˢ finiteStage n  ×  P m 
               × ((b : S)   b ∈ˢ finiteStage n    P b 
                            before n b m   Empty.⊥))

证明在点的层面运行搜索并拆包结果。least 作用于提升后的谓词与重新包装的截断见证,返回显式的对:一个点 m 及其 Least 证书。随后把点的三个分量与证书的两个分量重新装配成集合层面的陈述,最小性子句由把证书作用于对 b , b∈ 而得。

  leastMem P h = found .fst .fst
               , ( found .fst .snd
                 , ( found .snd .fst
                   ,  b b∈ pb hb  found .snd .snd (b , b∈) pb hb) ) )
    where

剩下的只是粘合:Q 在点的底层集合处读取集合层面的谓词,found 以从三元组重新包装成「一个点加一个证明」的截断前提调用 least。这条 leastMem 正是递归在后继层作为 baseLeast 喂给 Difference 的东西,它把搜索机制与最先分歧序之间的环节闭合。

    Q : Point n  hProp (ℓ-suc )
    Q a = P (a .fst)
    found : Σ[ m  Point n ] Least Q m
    found = least Q (PT.map  { (a , a∈ , pa)  (a , a∈) , pa }) h)

递归在第零层取空点名册,两条序律由空性成立。到后继层,上一层的点名册经可定义幂集提升。最先分歧比较利用两条子集前提给出三歧,并直接从上一层的序推出传递性;只有在成员关系需要于该层与其可定义幂集之间转换时,才使用后继层恒等式。

基例把三个字段装配成一个记录,三者正是刚建立的三件小事。点名册 empty 的长度为零:索引类型 Fin zero 为空,故条目与成员字段都用荒谬模式给出,即从无可能实参出发的函数。第零层没有可列的东西,这就是该点名册的全部内容。

stageOrder : (n : )  StageOrder n
stageOrder zero = record { tally = empty ; tri = triZero ; trans = transZero }
  where
  empty : Tally (finiteStage zero)
  empty = record

第零层点名册的其余字段来自同一个事实。任何被列条目的成员证书都不可能出现,因为根本没有索引;而层的覆盖则由 zero-empty 给出:假设 finiteStage zero 有成员便导出矛盾。因此,empty 在两个方向上都确实枚举了空层。

    { size   = zero
    ; item   = λ ()
    ; inside = λ ()
    ; onto   = λ x x∈  Empty.rec (zero-empty x x∈) }
  triZero : (x y : S)   x ∈ˢ finiteStage zero    y ∈ˢ finiteStage zero 

两条序字段都是空洞的。零处的三歧收到 xy 的成员证书,但这样的证书不存在,zero-empty 从第一个提取矛盾并了结目标。零处的传递收到类型为 before zero x y 的前提,按 before 的计算规则它是假真值,由 Empty.rec* 消去。空前提给出空结论;除「这个序是空的」之外,没有使用空序的任何性质。

           Tri  before zero x y  (x  y)  before zero y x 
  triZero x y x∈ y∈ = Empty.rec (zero-empty x x∈)
  transZero : (x y z : S)   before zero x y    before zero y z 
              before zero x z 
  transZero x y z h k = Empty.rec* h

后继步需要层 n 的三类数学输入:其最小元原理、before n 的三歧与传递性,以及其成员的一份点名册。前两类使最先分歧成为该层诸子集上的严格比较,点名册则通过布尔掩码枚举这些子集;三者共同给出层 suc n 所需的点名册与序律。

stageOrder (suc n) = record { tally = raised ; tri = triSuc ; trans = transSuc }
  where
  module Prev = Ordered n (stageOrder n)
  module Diff = Difference (before n) (finiteStage n) Prev.tri Prev.trans Prev.leastMem
  module Power = PowerStep (# n) (numeral-ord n) Prev.tally

认同 step路径 Lset-suc (# n),它断言 n 的后继层就是层 n 的可定义幂集。新点名册 raised 保留幂集点名册的长度与条目,因此枚举的是同样的可定义子集;改变的只是成员证书从何处读取,这正是 step 进入之处。

  step : finiteStage (suc n)  𝒟ₒ (finiteStage n)
  step = Lset-suc (# n)

  raised : Tally (finiteStage (suc n))
  raised = record
    { size   = Tally.size Power.powerTally

inside 字段把每份成员证书沿 step 的逆向从可定义幂集传输到后继层,因为证书证明的是在幂集中的成员关系,而点名册声称的是在 Lset (# suc n) 中的成员关系。对称地,onto 取后继层的成员证书,先沿 step 向前传输,再调用幂集点名册的覆盖。两个方向的传输都只作用于一句成员陈述,别无其他。

    ; item   = Tally.item Power.powerTally
    ; inside = λ i  subst  w   Tally.item Power.powerTally i ∈ˢ w ) (sym step)
                       (Tally.inside Power.powerTally i)
    ; onto   = λ x x∈  Tally.onto Power.powerTally x
                          (subst  w   x ∈ˢ w ) step x∈) }

辅助引理 members 提取 precedes-tri 所要求的包含前提。层 n 的可定义子集的成员都在层 n 中;这就是 𝒟ₒ∋⊆,从可定义幂集中的成员关系反向读出。先沿 stepx证书传输进幂集,所得是一个函数:对 x 的每个成员 w,给出 w 落在层 n 中的证书

  members : (x : S)   x ∈ˢ finiteStage (suc n) 
           (w : S)   w ∈ˢ x    w ∈ˢ finiteStage n 
  members x x∈ = 𝒟ₒ∋⊆ (finiteStage n) x (subst  v   x ∈ˢ v ) step x∈)

  triSuc : (x y : S)   x ∈ˢ finiteStage (suc n)    y ∈ˢ finiteStage (suc n) 
          Tri  before (suc n) x y  (x  y)  before (suc n) y x 

两条后继字段现在都是一行的应用。triSucDiff.precedes-tri,两条包含前提由 members 提供,因为 before (suc n) 按定义就是 precedes (before n) (finiteStage n)transSuc 逐字就是 Diff.precedes-trans,其前提本就具有正确的形状。递归就此闭合:每层的序事实都是上一层的序事实,被最先分歧理论所消费。

  triSuc x y x∈ y∈ = Diff.precedes-tri x y (members x x∈) (members y y∈)

  transSuc : (x y z : S)   before (suc n) x y    before (suc n) y z 
             before (suc n) x z 
  transSuc = Diff.precedes-trans

极限层

Lset ω 的每个成员取得其最小有穷层号;先比较层号、再比较局部层序,便得到极限层良序。

Lset ω 的成员会出现在某个由数码索引的有穷层。在它出现的诸层中,自然数的最小元搜索给出最小者,称为该元素的层号。后文组织不同层号之间的下降时,还会再次使用自然数的良基性。

极限的成员被包装成 Limit:一个集合连同它在 Lset ω 中的成员证书。引理 inSome 把这样的证书转换成一句截断的陈述:该集合出现在某个有穷层。从极限层读出证书,仅仅给出某个属于 ωδ,使该集合是 Lset δ 的可定义子集;外层消去的目标是截断类型,而截断类型是命题,故消去正当。

Limit : Type (ℓ-suc )
Limit = Σ[ x  S ]  x ∈ˢ Lset ω 

inSome : (x : S)   x ∈ˢ Lset ω    Σ[ n   ]  x ∈ˢ finiteStage n  ∥₁
inSome x h = PT.rec PT.squash₁ atStage (Lset-out ω x h)
  where

还需识别 ω 以下的索引 δ。隶属 δ ∈ ω 是一条截断陈述:存在提升后的自然数 k,使 δ 等于数码 # (lower k)。用 PT.map 在截断内取得该数码见证后,路径把关于 𝒟ₒ (Lset δ) 的可定义子集证书改写到 Lset (# lower k) 上;再由 Lset-sucx 放入 finiteStage (suc (lower k))。这证明了元素出现在有穷层,同时没有混淆索引 ω 与层 Lset ω

  atStage : Σ[ δ  S ] ( δ ∈ˢ ω  ×  x ∈ˢ 𝒟ₒ (Lset δ) )
            Σ[ n   ]  x ∈ˢ finiteStage n  ∥₁
  atStage (δ , δ∈ω , x∈) = PT.map named δ∈ω
    where
    named : Σ[ k  Lift  ] (# (lower k)  δ)  Σ[ n   ]  x ∈ˢ finiteStage n 

数码命名之后,named 产出实际的出现层。由于 Lset-sucLset (# (suc k)) 认同为 Lset (# k) 的可定义幂集,「该集合是 Lset (# (lower k)) 的可定义子集」的证书沿 sym (Lset-suc ...) 传输为 finiteStage (suc (lower k)) 中的成员关系。所以索引出现层的数码比出现在 ω 内部的数码多一,这正是索引与其后继层之间常见的差一。

    named (k , q) = suc (lower k)
      , subst  w   x ∈ˢ w ) (sym (Lset-suc (# (lower k))))
          (subst  w   x ∈ˢ 𝒟ₒ (Lset w) ) (sym q) x∈)

levelData : (a : Limit)
           Σ[ n   ] IsLeast natOrder  m  a .fst ∈ˢ finiteStage m) n

levelData 正是截断存在与最小元定理相遇之处。它对谓词 m ↦ a .fst ∈ˢ finiteStage m 与截断见证 inSome 应用自然数序的 leastOf 与排中律,返回一个显式数码连同 IsLeast 数据:该数码处的层含有该集合,且更小的数码都没有该性质。因此层号是最小的出现层,而非从截断中任意选出的层。

levelData a =
  leastOf natOrder lem  m  a .fst ∈ˢ finiteStage m) (inSome (a .fst) (a .snd))

level : Limit  
level a = levelData a .fst

level-in : (a : Limit)   a .fst ∈ˢ finiteStage (level a) 

两个投影有方便的名字:level a 是底层集合出现的最小数码,level-in a 是该层处的成员证书。极限序所需的关于成员「楼层」的一切现在都已是数据,下一节将恰好用这两个材料构造那个序。

level-in a = levelData a .snd .fst

极限上的序先按层号比较:层号较低的成员在前,同层的两个成员则按该层自己的序比较。第二支处理层号相等的情形,其方向使得第二个成员可以在第一个成员的层上读出;正因如此,定义中不出现任何跨层的转换。

非自反与传递是对那一支的分情形,层号等式的情形直接使用相应层上的层序事实。三歧先比较层号,仅当层号相同时才由层序判定。

关系 a ≺ b 是「占先」的两种方式的不相交和。左支说 a 的层号严格更小;右支说两层号相等,并且在层 level a 内,两个底层集合处于该层自己的 before 序中。左支的 Lift 把自然数上的比较从 Type ℓ-zero 抬升到 Type (ℓ-suc ℓ),即右支本已所在的宇宙,于是两支共用一个类型。这个关系按字典序读:层号分出高下,唯有打平时才去问层。

_≺_ : Limit  Limit  Type (ℓ-suc )
a  b = Lift {ℓ-zero} {ℓ-suc } (level a < level b)
       ((level b  level a) ×  before (level a) (a .fst) (b .fst) )

limit-irrefl : (a : Limit)  a  a  Empty.⊥
limit-irrefl a (inl h)       = ¬m<m (lower h)

非自反性对每一支分别用相应成分的事实处理:严格不等式 level a < level a¬m<m 拒绝;对 a 自身的 before (level a) 见证被 before-irrefl 拒绝,而后者在每层都成立且无需归纳。传递性则按两个前提各取哪一支来分情形。若两步都在层号上下降,<-trans 复合两个不等式;若只有一步在层号上下降,就用另一前提中的层号等式配合 subst,把那条严格不等式搬到正确的端点,结果仍在左支。

limit-irrefl a (inr (_ , h)) = before-irrefl (level a) (a .fst) h

limit-trans : (a b c : Limit)  a  b  b  c  a  c
limit-trans a b c (inl h)       (inl k)       = inl (lift (<-trans (lower h) (lower k)))
limit-trans a b c (inl h)       (inr (q , _)) =
  inl (lift (subst  j  level a < j) (sym q) (lower h)))

当两个前提都取同层支时,a ≺ b 给出 q : level b ≡ level ab ≺ c 给出 p : level c ≡ level b。复合 p ∙ q : level c ≡ level a 正是 a ≺ c 所需的等式。层序事实 hbc 陈述在 level b;沿 q 传输后落到 level a,便可由 StageOrder.transhab 复合。

limit-trans a b c (inr (q , _)) (inl k)       =
  inl (lift (subst  j  j < level c) q (lower k)))
limit-trans a b c (inr (q , hab)) (inr (p , hbc)) = inr (p  q , joined)
  where
  moved :  before (level a) (b .fst) (c .fst) 

moved 中的传输沿等式 q 移动 hbc,只改变 before 陈述所处的层,从 level b 换到 level a。此后两个见证便同处一层:hab 说在该层中 a 的集合先于 b 的,movedb 的先于 c 的,于是在层 level a 处用 StageOrder.trans 把二者接成 joined,即 a 在自己层内先于 c 的见证。传递性至此完成;接着陈述三歧性,其判定方式是直接比较两层号。

  moved = subst  j   before j (b .fst) (c .fst) ) q hbc
  joined :  before (level a) (a .fst) (c .fst) 
  joined = StageOrder.trans (stageOrder (level a)) (a .fst) (b .fst) (c .fst) hab moved

limit-tri : (a b : Limit)  Tri (a  b) (a  b) (b  a)
limit-tri a b = byLevel (level a  level b)

三歧性先判定 level a ≟ level b。层号不等时立即得到相应的严格比较支。相等支给出 p : level a ≡ level b;沿 sym p 传输 level-in b,便把 b 放入 finiteStage (level a),于是 StageOrder.tri 能在同一层比较两个底层集合。

  where
  byLevel : NatOrder.Trichotomy (level a) (level b)  Tri (a  b) (a  b) (b  a)
  byLevel (NatOrder.lt h) = lt (inl (lift h))
  byLevel (NatOrder.gt h) = gt (inl (lift h))
  byLevel (NatOrder.eq p) = same

局部三歧按极限关系所需的方向重新打包。若局部结果是 a before b,就在 a ≺ b 的同层支中返回 sym p : level b ≡ level a;若结果是 b before a,就在 b ≺ a 的同层支中返回 p,并把 before 证明传输到层 level b。底层集合相等可提升为 Limit 中的相等,因为成员证明分量是命题。

    (StageOrder.tri (stageOrder (level a)) (a .fst) (b .fst) (level-in a) b∈)
    where
    b∈ :  b .fst ∈ˢ finiteStage (level a) 
    b∈ = subst  j   b .fst ∈ˢ finiteStage j ) (sym p) (level-in b)
    same : Tri  before (level a) (a .fst) (b .fst)  (a .fst  b .fst)

重新包装按该层的判定分三种。若 a 的集合先于 b 的,结果是 的右支,并以 sym p 供给等式,方向恰是定义所要求的。若两集合相等,Σ≡Prop 把它提升为配对 ab 之间的路径;这是合法的,因为 Limit 的第二个分量是命题,这就是 eq 情形。若 b 的集合先于 a 的,则沿 p 把该 before 事实传输到它应被陈述的层号处,结果是以相反实参给出的右支。这里的每种情形都没有用到已造好的成分之外的任何东西。

                before (level a) (b .fst) (a .fst) 
          Tri (a  b) (a  b) (b  a)
    same (lt h) = lt (inr (sym p , h))
    same (eq q) = eq (Σ≡Prop  z  snd (z ∈ˢ Lset ω)) q)
    same (gt h) = gt (inr (p , subst  j   before j (b .fst) (a .fst) ) p h))

良基性的证明是两层嵌套的归纳,而把它们分开是有意的。外层是对层号的归纳,采用库中现成的封装,它提供一条覆盖所有更低层的归纳假设。内层沿有穷层已有的可及性作普通的下降,其合法性正来自该层的有穷性。跨层下降的一步使用外层假设,层内的一步使用内层假设;内层函数除自己的可及性实参外不沿任何东西递归,因此二者从不需要同时比较。

固定层号为 k 的目标 b,以及层 k 中与它底层集合相同的点 u。内层论证把 u 关于局部关系 Below k 的可及性转成 b 关于极限关系的可及性。展开 Acc 后,任意前驱记为 c。若 c 的层号更低,就使用外层归纳假设;若层号相同,就把它变成 u 的局部前驱并使用内层可及性。

accInside : (k : )
           ((m : )  m < k  (b : Limit)  level b  m  Acc _≺_ b)
           (u : Point k)  Acc (Below k) u
           (b : Limit)  level b  k  b .fst  u .fst  Acc _≺_ b
accInside k ih u (acc ru) b q e = acc step

完成 step 按前提 c ≺ b 所取的支分情形。左支中,c 的层号严格小于 b,因而小于 k;该不等式用 subst 在等式 q 之下搬动,然后在层号 level c 处使用 ih,这正是跨层的情形。右支中,cb 同层号,故二者都在层 k 之内,下降便交给内层可及性:ruu 的可及性的 acc 构造子所提供的函数,把它作用于与 c 对应的点 pc 以及 pc 位于 u 之下的证明。

  where
  step : (c : Limit)  c  b  Acc _≺_ c
  step c (inl h) = ih (level c) (subst  j  level c < j) q (lower h)) c refl
  step c (inr (qb , hc)) = accInside k ih pc (ru pc below) c qc refl
    where

右支的簿记需要显式写出。首先 qc 复合两条层号等式,即 sym qbq,证明 level c ≡ k;正是这一点使 c 能被放到层 k 中看。然后 pcc 的底层集合与它在层 k 中的隶属打包在一起,该隶属由 level-in c 沿 qc 传输得到。Point k 就是一个集合连同这样的证书,所以这一个构造把论证从极限带回内层序所在的有穷层。

    qc : level c  k
    qc = sym qb  q
    pc : Point k
    pc = c .fst , subst  j   c .fst ∈ˢ finiteStage j ) qc (level-in c)
    below : Below k pc u

在层号相同的情形,每个极限前驱 b 都与 u 位于同一有穷层 k,并在该层序中低于 u。该分支携带的等式只把两个端点对齐到固定的 k;随后 u 关于 Below k 的可及性给出 b 的可及性。因此,内层递归只沿一个有穷层的序下降。

    below = subst  v   before k (c .fst) v ) e
              (subst  j   before j (c .fst) (b .fst) ) qc hc)

accByLevel : (k : )  (b : Limit)  level b  k  Acc _≺_ b
accByLevel = WFI.induction <-wellfounded outer
  where

外层是对自然数层号的良基归纳,其归纳假设处理层号严格小于 k 的前驱;内层可及性处理仍处于层号 k 的前驱。两种情形合成字典序式的证明,无须假设不同有穷层上的序彼此相容。

  outer : (k : )  ((m : )  m < k  (b : Limit)  level b  m  Acc _≺_ b)
         (b : Limit)  level b  k  Acc _≺_ b
  outer k ih b q = accInside k ih here
    (Ordered.wellFounded k (stageOrder k) here) b q refl
    where

outer 的主体把目标化归到内层引理。它先造出 here,即与 b 对应的层 k 的点,其造法与上面的 pc 完全相同;然后 Ordered.wellFounded k (stageOrder k) here 提供该点在层 k 序中的可及性,accInside 便由此接手,其余两个实参是层号等式 q 以及把 b 的底层集合与 here 的认同起来的自反等式。最后的陈述 limit-wf 说极限的每个成员都可及,做法是在层号 level a 处以平凡等式 refl 实例化层号归纳。

    here : Point k
    here = b .fst , subst  j   b .fst ∈ˢ finiteStage j ) q (level-in b)

limit-wf : WellFounded _≺_
limit-wf a = accByLevel (level a) a refl

limitOrder : SWO Limit

因此,Limit 上的严格良序:它满足三歧、非自反与传递,而两层归纳证明其良基性。首次出现层号不同的元素按层号排序;只有层号相同的元素才由一个有穷层序比较。

limitOrder = record
  { _<∙_   = _≺_
  ; tri∙   = limit-tri
  ; irr∙   = limit-irrefl
  ; trans∙ = limit-trans

因此,limitOrderLset ω 诸成员上的严格良序:层号是主键,最小层号相同的元素由该有穷层的序比较。于是,它的最小元运算可从极限层上任意仅仅非空的命题值族中作出选取。

  ; wf∙    = limit-wf }

小结

有穷点名册沿可定义幂集上升,支撑每个数码层处良基的最先分歧序,并最终给出 Lset ω 上的 limitOrder

Tally 就是本章拥有的全部有穷性:一个命中每个成员的有穷族,既不要求单射,也不要求可判定的相等。PowerStep.powerTally 把它抬到可定义幂集上,办法是枚举点名册上的位向量,并指出已清点层的每个子集都可定义;stageOrder 随后沿诸数码跑完这一步,于是每个有穷层都有一份点名册。

precedes 在两个子集最先分歧之处比较它们。它的非自反性直接由定义推出,传递性由比较两个见证得到,三歧则由排中律连同基底的最小元得到。良基性并不单由这条比较的定义推出;在这里,它经由 Search 从点名册得到。自然数子集上的下降链例子说明了为何有穷层这一假设不可省略。

limitOrderLset ω 诸成员上的一个严格良序,以层号为主键,层内则用各有穷层自己的序。它就是选择公理将要取用的接口:有了它,leastOf 能从极限层诸成员的任一非空性质中挑出一个成员,且每次挑出同一个。