使用以下作为示例
postulate DNE : {A : Set} → ¬ (¬ A) → A
data ∨ (A B : Set) : Set where
inl : A → A ∨ B
inr : B → A ∨ B
-- Use double negation to prove exclude middle
classical-2 : {A : Set} → A ∨ ¬ A
classical-2 = λ {A} → DNE (λ z → z (inr (λ x → z (inl x)))
我知道这是正确的,纯粹是因为agda是如何工作的,但我是这种语言的新手并且不能理解它的语法是如何工作的,如果有人能指引我完成任务,我将不胜感激在,谢谢:))
我有haskell的经验,虽然那是一年前的事。
答案 0 :(得分:6)
让我们从假设开始。语法很简单:
postulate name : type
这断言存在一些名为type
的{{1}}类型的值。把它想象成逻辑中的公理 - 被定义为真实且不被质疑的事物(在这种情况下由Agda提出)。
接下来是数据定义。 mixfix声明略有疏忽,所以我会解决它并解释它的作用。第一行:
name
引入一个名为data _∨_ (A B : Set) : Set where
的新类型(构造函数)。 _∨_
接受_∨_
类型的两个参数,然后返回Set
。
我将它与Haskell进行比较。在以下示例中,Set
和A
或多或少等同于B
和a
:
b
这意味着数据定义定义了多态类型(模板或通用,如果您愿意)。 data Or a b = Inl a | Inr b
是Haskell Set
的Agda等价物。
下划线有什么用? Agda允许您定义任意运算符(前缀,后缀,中缀...通常仅由单个名称 - mixfix调用)。下划线告诉Agda参数在哪里。使用前缀/后缀运算符最好看到这一点:
*
您甚至可以创建疯狂的运算符,例如:
-_ : Integer → Integer -- unary minus
- n = 0 - n
_++ : Integer → Integer -- postfix increment
x ++ = x + 1
下一部分是数据构造函数本身的定义。如果您已经看过Haskell的GADT,这或多或少是相同的。如果你还没有:
当你在Haskell中定义一个构造函数时,比如上面的if_then_else_ : ...
,你只需指定参数的类型,Haskell就会找出整个事物的类型,即Inr
。当您在Agda中编写GADT或定义数据类型时,您需要指定整个类型(并且有充分的理由,但我现在不会介绍它。)
因此,数据定义指定了两个构造函数Inr :: b -> Or a b
,inl
类型和A → A ∨ B
类型inr
。
现在出现了有趣的部分:B → A ∨ B
的第一行是一个简单的类型声明。 classical-2
的事情是怎么回事?当您在Haskell中编写多态函数时,您只需使用小写字母来表示类型变量,例如:
Set
你的真正含义是:
id :: a -> a
你真正的意思是:
id :: forall a. a -> a
即。它不仅仅是id :: forall (a :: *). a -> a
,而是a
是一种类型。 Agda让你做这个额外的步骤并明确地声明这个量化(这是因为你可以量化更多的东西而不仅仅是类型)。
花括号?让我再次使用上面的Haskell示例。当您在某处使用a
函数时,请说id
,您无需指定id 5
。
如果您使用普通的paretheses,则每次调用a = Integer
时都必须提供实际类型A
。但是,大多数情况下,类型可以从上下文中推断出来(很像上面的classical-2
示例),因此对于这些情况,您可以“隐藏”参数。然后Agda尝试自动填写 - 如果不能,它会抱怨。
最后一行:id 5
是Agda说λ x → y
的方式。这应该可以解释大部分内容,唯一剩下的就是花括号了。我相当肯定你可以在这里省略它们,但无论如何:隐藏的论据做他们所说的 - 他们隐藏。因此,当您定义从\x -> y
到{A}
的函数时,您只需提供B
类型的内容(因为隐藏了B
)。在某些情况下,您需要知道隐藏参数的值,这就是这种特殊的lambda所做的事情:{A}
允许您访问隐藏的A!