在Agda中使用依赖对的问题

我正在学习 Agda by tutorial,现在我正在阅读有关依赖对.

所以,这是代码片段:

data Σ (A : Set) (B : A → Set) : Set where
  _,_ : (a : A) → (b : B a) → Σ A B

infixr 4 _,_

Σprojₗ : {A : Set}{B : A → Set} → Σ A B → A
Σprojₗ (a,b) = a

data _∈_ {A : Set}(x : A) : List A → Set where
  first : {xs : List A} → x ∈ x ∷ xs
  later : {y : A}{xs : List A} → x ∈ xs → x ∈ y ∷ xs

infix 4 _∈_

_!_ : ∀{A : Set} → List A → ℕ → Maybe A
  [] ! _           = nothing
  x ∷ xs ! zero    = just x
  x ∷ xs ! (suc n) = xs ! n

infix 5 _!_

lookup : ∀ {A}{x : A}(xs : List A) → x ∈ xs → Σ ℕ (λ n → xs ! n ≡ just x)

_,_是从属对的构造函数,Σprojₗ返回对的第一部分,_∈_是成员关系,lst!如果列表的长度大于或等于i,则返回$(第i个元素),否则为no.我想要写的查找函数,它接受列表XS,会员的x∈XS的证明,并返回取决于对自然数和功能,其为自然数的n个返回的事实,即列表的第n个元素是证明(或反证)只是x.现在功能看起来像

lookup : ∀ {A}{x : A}(xs : List A) → x ∈ xs → Σ ℕ (λ n → xs ! n ≣ just x)
lookup {A} {x} .(x ∷ xs) (inHead {xs}) = 0,refl
lookup .(y ∷ xs) (inTail {y} {xs} proof) = (1 + Σprojₗ (lookup xs proof)),?

我想我应该写一些函数,比如Σprojᵣ(它应该返回对的第二个元素,带有签名A→Set的函数)来填充空洞,但我不知道如何编写它.唯一的变形是typechecked

Σprojᵣ : {A : Set}{B : A → Set} → Σ A B → (A → Set)
Σprojᵣ {A} {B} (a,b) = B

,但我没有管理如何用这个完成查找功能.我该如何解决这个练习?

实际上,假设Σprojᵣ投射该对的第二个元素,Σprojᵣ(查找xs证明)是适合孔的正确解.问题是,如何编写这个投影?

如果我们有普通的非依赖对,那么编写两个投影很容易:

data _×_ (A B : Set) : Set where
  _,′_ : A → B → A × B

fst : ∀ {A B} → A × B → A
fst (a,′ b) = a

snd : ∀ {A B} → A × B → B
snd (a,′ b) = b

当我们使用依赖对时,是什么让它变得如此困难?提示隐藏在名称中:第二个组件取决于first的值,我们必须以某种方式在我们的类型中捕获它.

所以,我们从:

data Σ (A : Set) (B : A → Set) : Set where
  _,_ : (a : A) → B a → Σ A B

编写左侧组件的投影很容易(请注意,我称之为proj 1而不是Σprojₗ,这就是标准库的作用):

proj₁ : {A : Set} {B : A → Set} → Σ A B → A
proj₁ (a,b) = a

现在,第二个投影应该看起来像这样:

proj₂ : {A : Set} {B : A → Set} → Σ A B → B ?
proj₂ (a,b) = b

但B什么?由于第二个组件的类型取决于第一个组件的值,我们不知何故需要通过B走私它.

我们需要能够参考我们的一对,让我们这样做:

proj₂ : {A : Set} {B : A → Set} (pair : Σ A B) → B ?

现在,我们这对的第一个组件是proj 1对,所以让我们填写:

proj₂ : {A : Set} {B : A → Set} (pair : Σ A B) → B (proj₁ pair)

事实上,这个样式!

但是,与手工编写proj 2相比,有更简单的解决方案.

我们可以将其定义为记录,而不是将Σ定义为数据.记录是只有一个构造函数的数据声明的特例.好消息是记录为您提供免费的预测:

record Σ (A : Set) (B : A → Set) : Set where
  constructor _,_
  field
    proj₁ : A
    proj₂ : B proj₁

open Σ  -- opens the implicit record module

这个(以及其他有用的东西)给你投影proj 1和proj 2.

我们也可以用声明解构对,并完全避免这个项目:

lookup : ∀ {A} {x : A}(xs : List A) → x ∈ xs → Σ ℕ (λ n → xs ! n ≡ just x)
lookup {x = x} .(x ∷ xs) (first {xs}) = 0,refl
lookup         .(y ∷ xs) (later {y} {xs} p) with lookup xs p
... | n,p′ = suc n,p′

with允许您不仅对函数的参数进行模式匹配,还对中间表达式进行模式匹配.如果你熟悉Haskell,它就像一个案例.

现在,这几乎是理想的解决方案,但仍然可以做得更好.请注意,我们必须将隐式{x},{xs}和{y}带入范围,以便我们可以记下点模式.点模式不参与模式匹配,它们被用作断言,这个特定的表达式是唯一适合的模式.

例如,在第一个等式中,点图案告诉我们列表必须看起来像x∷xs – 我们知道这是因为我们在证明上匹配模式.由于我们只对证明进行模式匹配,因此list参数有点多余:

lookup : ∀ {A} {x : A} (xs : List A) → x ∈ xs → Σ ℕ (λ n → xs ! n ≡ just x)
lookup ._ first = 0,refl
lookup ._ (later p) with lookup _ p
... | n,p′

编译器甚至可以推断出递归调用的参数!如果编译器可以自己计算这些东西,我们可以安全地将其标记为隐式:

lookup : ∀ {A} {x : A} {xs : List A} → x ∈ xs → Σ ℕ (λ n → xs ! n ≡ just x)
lookup first = 0,refl
lookup (later p) with lookup p
... | n,p′

现在,最后一步:让我们带来一些抽象.第二个等式将该对分开,应用一些函数(suc)并重建该对 – 我们在该对上映射函数!

现在,地图的完全依赖类型非常复杂.如果你不明白,不要气馁!当你以后回来了解更多知识时,你会发现它很有吸引力.

map : {A C : Set} {B : A → Set} {D : C → Set}
      (f : A → C) (g : ∀ {a} → B a → D (f a)) →
      Σ A B → Σ C D
map f g (a,b) = f a,g b

与之比较:

map′ : {A B C D : Set}
       (f : A → C) (g : B → D) →
       A × B → C × D
map′ f g (a,′ b) = f a,′ g b

我们以非常简洁的方式总结:

lookup : ∀ {A} {x : A} {xs : List A} → x ∈ xs → Σ ℕ (λ n → xs ! n ≡ just x)
lookup first     = 0,refl
lookup (later p) = map suc id (lookup p)

也就是说,我们将suc映射到第一个组件,并保持第二个组件不变(id).

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结