安全数组索引
Array 和 Nat 的 GetElem 实例需要证明提供的 Nat 小于数组。
在实践中,这些证明通常最终会连同索引一起传递给函数。
与其分别传递索引和证明,可以使用名为 Fin 的类型将索引和证明捆绑到单个值中。
这可以使代码更易阅。此外,许多对数组的内置操作将其索引参数作为 Fin 而非 Nat,
因此使用这些内置操作需要了解如何使用 Fin。
类型 Fin n 表示严格小于 n 的数字。换句话说,Fin 3 描述 0、1 和 2,
而 Fin 0 没有任何值。Fin 的定义类似于 Subtype,因为 Fin n 是一个包含 Nat
和小于 n 的证明的结构体:
structure Fin (n : Nat) where
val : Nat
isLt : LT.lt val n
Lean 包含 ToString 和 OfNat 的实例,允许将 Fin 值方便地用作数字。
换句话说,#eval (5 : Fin 8)
的输出为 5,
而非类似 {val := 5, isLt := _} 的值。
当提供的数字大于边界时,OfNat 实例对于 Fin 不会失败,而是返回一个对边界取模的值。
这意味着 #eval (45 : Fin 10) 的结果是 5,而非编译时错误。
在返回类型中,将 Fin 作为找到的索引返回,能够让它与其所在的数据结构的连接更加清晰。
上一节中的 Array.find 返回一个索引,
调用者不能立即使用它来执行数组查找,因为有关其有效性的信息已丢失。
更具体类型的值可以直接使用,而不会使程序变得复杂得多:
def findHelper (arr : Array α) (p : α → Bool) (i : Nat) : Option (Fin arr.size × α) :=
if h : i < arr.size then
let x := arr[i]
if p x then
some (⟨i, h⟩, x)
else findHelper arr p (i + 1)
else none
termination_by findHelper arr p i => arr.size - i
def Array.find (arr : Array α) (p : α → Bool) : Option (Fin arr.size × α) :=
findHelper arr p 0
练习
编写一个函数 Fin.next? : Fin n → Option (Fin n) 当 Fin 在边界内时返回下一个最大的 Fin,
否则返回 none。检查
#eval (3 : Fin 8).next?
会输出
some 4
而
#eval (7 : Fin 8).next?
会输出
none