假设我们正在编写一个 lambda 演算的实现,作为其中的一部分,我们希望能够选择一个新的非冲突名称:
record Ctx where
constructor MkCtx
bindings : List String
emptyCtx : Ctx
emptyCtx = MkCtx []
addCtx : String -> Ctx -> Ctx
addCtx name = record { bindings $= (name ::) }
pickName : String -> Ctx -> (String, Ctx)
pickName = go Z
where
mkName : Nat -> String -> String
mkName Z name = name
mkName n name = name ++ show n
go n name ctx = let name' = mkName n name in
if name' `elem` bindings ctx
then go (S n) name ctx
else (name', addCtx name' ctx)
由于 中的递归路径, Idris 总体检查器认为pickName
不是总体的,这是go
正确的:确实,总体的证明不依赖于任何在语法上更小的术语,而是依赖于观察到如果bindings
有k
元素,那么它将不需要不仅仅是k + 1
递归调用来找到一个新的名字。但是如何用代码来表达呢?
我也倾向于外部验证,首先编写一个函数,然后编写一个(类型检查,但从不执行)证明它具有正确的属性。在这种整体情况下是否有可能pickName
?
受@HTNW 启发,看起来正确的方法是使用 aVect
而不是列表。从向量中删除元素将使其大小(以类型表示)在语法上更小,避免自己证明的需要。所以,(稍微重构)版本pickName
将是
pickName : String -> Vect n String -> String
pickName name vect = go Z vect
where
mkName : Nat -> String
mkName Z = name
mkName n = name ++ show n
go : Nat -> Vect k String -> String
go {k = Z} n _ = mkName n
go {k = (S k)} n vect' =
let name' = mkName n in
case name' `isElem` vect' of
Yes prf => go (S n) $ dropElem vect' prf
No _ => name'