在阿格达(Agda)中,似乎经常有两种方法来完善一套。一种是通过简单地编写一个检查属性是否成立并提升的函数。例如:
has_true : List Bool -> Bool
has_true (true ∷ xs) = true
has_true (false ∷ xs) = has_true xs
has_true [] = false
Truthy : List Bool -> Set
Truthy list = T (has_true list)
在这里,Truthy list
证明布尔列表至少具有一个真实元素。另一种方法是直接将该属性编码为归纳类型:
data Truthy : List Bool -> Set where
Here : (x : Bool) -> (x ≡ true) -> (xs : List Bool) -> Truthy (x ∷ xs)
There : (x : Bool) -> (xs : List Bool) -> Truthy xs -> Truthy (x ∷ xs)
在这里,Truthy list
也是一样。
我相信我之前已经阅读过一个比较,但我不记得了。这些不同的样式有名称吗?使用一种样式优于另一种样式有什么优点和缺点?还有第三种选择吗?
答案 0 :(得分:6)
到目前为止,您列出了两种定义谓词的方法:
A -> Bool
函数。我还要再添加一个:
A -> Set
函数。也可以称为“递归定义”或“通过大消除定义”。第三个版本是Agda中的以下版本:
open import Data.Bool
open import Data.Unit
open import Data.Empty
open import Data.List
hastrue : List Bool → Set
hastrue [] = ⊥ -- empty type
hastrue (false ∷ bs) = hastrue bs
hastrue (true ∷ bs) = ⊤ -- unit type
首先,让我们谈谈使用这三个选项的哪种谓词可表示。这是一个ASCII表。 *
是通配符,表示是/否。
| P : A -> Set | P : A -> Bool | data P : A -> Set |
|-------------------|--------------|---------------|-------------------|
| Proof irrelevant | * | yes | * |
| Structural | yes | yes | * |
| Strictly positive | * | N/A | yes |
| Decidable | * | yes | * |
证明不相关表示P x
的所有证明都是相同的。在Bool
情况下,证明通常是一些p : P x ≡ true
或p : IsTrue (P x)
与IsTrue = λ b → if b then ⊤ else ⊥
,并且在两种情况下,所有证明确实相等。我们可能希望也可能不希望谓词不相关。
结构表示P x
只能使用结构上小于A
的{{1}}元素来定义。函数始终是结构性的,因此,如果某些谓词不是结构性的,则只能归纳定义。
严格肯定的表示x
不能递归出现在功能箭头的左侧。非严格的肯定谓词不能归纳定义。与证明相关的非严格正谓词的一个示例是对功能类型的代码的解释:
P
判定是不言自明的; data Ty : Set where
top : Ty
fun : Ty → Ty → Ty
⟦_⟧ : Ty → Set
⟦ top ⟧ = ⊤
⟦ fun A B ⟧ = ⟦ A ⟧ → ⟦ B ⟧ -- you can't put this in "data"
函数必须是可判定的,这使它们不适用于不可判定的谓词或不能轻易地作为结构性A -> Bool
函数写出来的谓词。可判定性的优点是排除了中间推理,而没有假设或其他可判定性证明的非Bool
谓词定义是不可能的。
第二,关于 Agda / Idris中的实际后果。
您可以对归纳谓词的证明进行从属模式匹配。使用递归和布尔谓词,您必须首先对Bool
值进行模式匹配,以使谓词见证计算。有时,这使归纳谓词变得方便,例如。您可能有一个包含10个构造函数的枚举类型,并且您希望谓词仅包含一个构造函数。归纳定义的谓词只允许您匹配真实情况,而其他版本则要求您始终匹配所有情况。
另一方面,布尔值和递归谓词会在您知道A
元素具有给定形状后自动进行计算。可以在Agda中使用它来使类型推断自动填充证明,而无需使用战术或实例。例如,只要A
是带有已知的hastrue xs
前缀的列表表达式,就可以通过对对和单位类型的eta规则来解决类型为xs
的空洞或隐式参数。类似于布尔谓词。