跳到内容

alhassy/gentle反思

文件夹和文件

姓名姓名
上次提交消息
上次提交日期

最新提交

 

历史

14承诺
 
 
 
 
 
 
 
 

存储库文件导航

对Agda中反射的缓慢介绍-战术!

**摘要**

两个不同定理的一个证明!

让我们学习如何在Agda中做到这一点。

本教程主要是对文档关于Agda的反射机制只公开反射接口并提供了几个小示例。本教程的目标是包含各种各样的示例,以及读者偶尔的练习。

示例包括:

  • 内置标识符名称的字符串操作。🍓
  • AST形成的便捷组合器:𝓋𝓇𝒶, λ𝓋_↦_, …. 🛠
  • 大量引用术语和类型的示例。🎯
  • 示例数据类型的单例类型的批量派生,以及可推导的证明💛 🎵
  • 自动化仅回流 具有模式匹配🏄
  • Agda中C风格宏的讨论🌵
  • 使用宏在不增加语法开销的情况下提取证明模式💪 🎼
  • 关于我不能做的事情的评论,可能是因为我不能做😭

这里的一切都适用于Agda 2.6.0版。此文档是使用(编码错误)org-agda公司框架。

纯洁的.agda(阿格达)可以找到文件在这里.

目录

  1. 进口
  2. 介绍
  3. 名称已知标识符的类型
  4. 精氨酸参数的类型
  5. 期限术语类型
    1. 示例:简单类型
    2. 示例:简单术语
    3. 之间的关系引用报价条款
    4. 示例:Lambda术语
  6. 使用Typechecking Monad进行元编程总费用
  7. 取消引用—创建新功能和类型
  8. Sidequest:避免单调乏味回流证据
  9. 宏——抽象证明模式
    1. C样式宏
    2. 枯燥重复的证据不再存在!
  10. 我们的第一个实证策略
  11. 编写宏的启发
  12. 在子表达式的深处怎么样?

进口

首先,一些必要的进口:

模块温和介绍反思将级别导入为级别打开导入反射隐藏(名称;类型)打开导入关系。二元的。命题等式隐藏([_])打开导入关系。一次使用(可决定)打开导入关系。Nullary公司打开导入数据。单位打开导入数据。Nat as Nat隐藏(_⊓_)打开导入数据。布尔打开导入数据。产品打开导入数据。作为列表列出打开导入数据。字符作为字符打开导入数据。字符串作为字符串

介绍

反思是将程序代码转换为抽象语法的能力,可以像其他任何数据结构一样进行操作的数据结构。

例如,考虑为枚举类型编写一个可判定等式的单调过程。除了繁琐和容易出错之外什么是机械衍生的概念模糊了其相应的一般原则,因此前述确保正确性或安全性保证的任何机器协助。反思可以提供更经济、更有纪律的方法。

本教程的目的是展示如何从Agda中的反射开始。据我所知,在这方面没有最新的教程。

Agda的反射机制主要有三种类型:名称、精氨酸、期限。我们将在以下是简单枚举类型以及其他标准类型。

data RGB:设置位置红、绿、蓝:RGB

名称已知标识符的类型

姓名是引用标识符Agda名称的类型。可以使用这个引用关键字。

a-name:名称a-name=quoteℕisNat:Name→BoolisNat(引号ℕ)=真isNat _=假--错误:设置→名称--错误的s=引号s{-s未知-}
  • 名称配备了平等、排序和显示功能。
  • Quote对函数参数无效;标识符必须是已知的。

让我们显示姓名:

_:showName(quote _≡_)≡“Agda.Buildin.Equality._≡__”_=回流_:showName(引号红色)≡“gentle-intro-to-reflection.RGB.Red”_=回流

如果有红色显示为“RGB.红色”.

首先,让我们介绍一些“编程”助手,将Agda字符串视为其中Haskell字符串,同样将谓词视为可判定。

{-类似于“$”,但用于字符串。-}__:(列表字符→列表字符)→字符串→字符串f⟨𝒮⟩s=从列表(f(到列表s)){-这应该在标准库中;我找不到它。-}到十二月:{У}{A:集У}→(p:A→Bool)→可决定{У){A}(λA→pa≡true)到12月p x和p xtoDec p x | false=无λ()toDec p x | true=是ref

我们现在可以很容易地获得模块的名称,然后将其从数据构造函数的名称中删除。

module-name:字符串模块名称=takeWhile(toDec(λc→not(c Char.=='.')))showName(引号红色)_:module-name≡“gentle-intro-to反射”_=回流strName:Name→StringstrName n=drop(1+String.length模块名称)⟨𝒮⟩showName n{-“1+”表示限定名中的“.”分隔符。-}_:strName(引号红色)≡“RGB.Red”_=回流

名称本质上为我们提供了已知名称的内部表示,我们可以查询以获取其定义或类型。稍后我们将展示如何获取从它的名字。

精氨酸参数的类型

Agda中的参数可能是隐藏的或在计算上无关的。此信息由精氨酸类型。

{-参数可以是(可见)、{隐藏}或⦃实例⦄-}数据可见性:设置位置可见隐藏实例:可见性{-参数可以是相关的也可以是无关的:-}数据相关性:设置位置相关无关:相关性{-可见性和相关性表征参数的行为:-}data ArgInfo:设置位置arg-info:(v:可见性)(r:相关性)→ArgInfodata Arg(A:Set):设置位置参数:(i:ArgInfo)(x:A)→参数A

例如,让我们创建一些帮助程序来生成任何给定类型的参数A类:

{-可见相关参数-}𝓋𝓇𝒶 : {A:设置}→A→参数A𝓋𝓇𝒶 = arg(arg-info可见相关){-𝒽idden𝓇elevant𝒶argument-}𝒽𝓇𝒶 : {A:设置}→A→参数A𝒽𝓇𝒶 = arg(arg-info隐藏相关)

以下是变量对应项,用于期限数据类型,这将在稍后讨论。

  • 变量是De Bruijn索引的,可以应用于参数列表。

  • 索引n个指的是n个远离“此处”的位置。

    {-可见相关变量-}𝓋𝓇𝓋 : (debuijn:ℕ)(args:列表(Arg术语))→Arg术语𝓋𝓇𝓋 n args=arg(arg-info可见相关)(var n args)

    {-𝒽idden𝓇elevant𝓋变量-}𝒽𝓇𝓋 : (debuijn:ℕ)(args:列表(Arg术语))→Arg术语𝒽𝓇𝓋 n args=arg(arg-info隐藏相关)(var n args)

期限术语类型

我们使用报价条款关键字转换类型良好的代码片段-具体语法-转换为期限数据类型-抽象语法。以下是对期限:

数据术语,其中{-变量具有De Bruijn索引,可以应用于参数。-}var:(x:ℕ)(args:List(Arg Term))→Term{-构造函数和定义可以应用于参数列表。-}con:(c:名称)(args:列表(参数术语))→术语def:(f:名称)(args:List(Arg Term))→Term{-λ-抽象绑定一个变量;“t”是变量的字符串名和羔羊的身体一起。-}lam:(v:可见性)(t:Abs术语)→术语{-Abs A≅字符串×A-}pat-lam:(cs:List子句)(args:List(Arg Term))→Term{-望远镜,或函数类型;λ-类型抽象。-}pi:(a:Arg类型)(b:Abs类型)→术语{-“Set n”或表示类型的某些术语-}agda排序:(s:sort)→术语{-元变量;通过quoteTerm-}引入meta:(x:meta)→列表(参数术语)→术语{-文字≅ℕ|Word64|Float|Char|String|Name|Meta-}lit:(l:文字)→术语{-本AST无法表示的项目;例如,孔。-}未知:术语{-在取消查询时被视为“_”。-}数据排序位置集合:(t:Term)→排序{-给定(可能是中性)级别的集合。-}lit:(n:Nat)→排序{-给定混凝土水平的集合。-}未知:排序数据子句,其中子句:(ps:列表(Arg模式))(t:术语)→子句荒谬的条款:(ps:列表(Arg模式))→条款

示例:简单类型

这里有三个“定义”的名称示例,前两个不带参数。最后一个采用了一个可见且相关的论点,这是字面上的自然。

导入数据。Vec作为V导入数据。Fin作为F_:quoteTermℕ≡def(引号\8469;)[]_=回流_:quoteTerm V.Vec≡def(引用V.Vec)[]_=回流_:quoteTerm(F.Fin 3)≡def(引用F.Fin)_=回流

示例:简单术语

基本数字引号:

_:quoteTerm 1≡lit(nat 1)_=回流_:quoteTerm(例如零)≡con(引用suc)(arg(arg-info可见相关)(引用项0)[])_=回流{-使用我们的助手\120007\119990\-}_:quoteTerm(suc zero)≡con(quote suc)(引用suc)_=回流

下面的第一个示例说明了真的是类型“con”结构体不需要任何论证[]第二个例子表明_≡_是已定义的名称,当前未应用于任何参数。最后一个例子将命题相等应用于两个论点。

_:quoteTerm true≡con(引号true)[]_=回流_:引号项_ lect _ lect def(引号_ lect _)[]_=回流_:quoteTerm(“b”≡“a”)Select-def(引号_ Select_)(定义(引用级别为零)[])●𝒽𝓇𝒶(def(引号字符串)[])●𝓋𝓇𝒶(亮起(字符串“b”))●𝓇𝒶(lit(字符串“a”))_=回流

请注意,命题等式实际上有四个参数——一个级别、一个类型和两个参数——前两者发生的地方从后者无法推断。下面是一个更具多态性的示例:

_:∀{level:level.level}{Type:Set level}(x y:Type)引用项(x≡y)≡def(引号≡)(𝒽𝓇𝓋 3 [] ∷ 𝒽𝓇𝓋 2 [] ∷ 𝓋𝓇𝓋 1 [] ∷ 𝓋𝓇𝓋 0 [] ∷ [])_=λx y→回流

记住De Bruijn索引n个指lambda变量那就是n+1lambdas远离其使用场所。例如,𝓋𝓇𝓋 1意味着从⋯ ≡ ⋯,开始1+1lambdas离开以获取变量x个.

我们将演示一个部分的示例,比如≡_“b”下面讨论lambda抽象时。

之间的关系引用报价条款

已知名称𝒻在引用的术语中用引用𝒻在AST表示中。

例如,我将把这个词用于我的假设项目

假设:集合假设𝒻:𝒜→ℬ_:quoteTerm𝒻≡def(引号\119995;)[]_=回流

相比之下变化用a表示无功功率,无功功率AST表示中的构造函数。

模块{AB:Set}{f:A→B}其中_:quoteTerm f≡var 0[]_=回流

示例:Lambda术语

首先,我们展示了lambda的缩减是如何工作的,然后我们展示了lambda的功能表示为期限值。

报价条款在生成期限值。

_:quoteTerm((λx→x)“nice”)≡lit(字符串“nice“)_=回流

发生Eta还原,f≈λx→f x.

id:{A:Set}→A→Aid x=x_:报价项(λ(x:ℕ)→id x)≡def(引号id)(def(quote id)[])б[])_=回流

没有发生增量减少;函数定义没有详细说明。

_:quoteTerm(id“a”)≡def(引号id)(def(引号字符串)[])_=回流

这是布尔函数的一个简单标识函数。带有名为“visible”“abs”tract参数的“lam”bda“x”已引入仅作为0最近界变量的主体,应用于空参数列表。

_:quoteTerm(λ(x:Bool)→x)≡lam可见(abs“x”(var 0[]))_=回流

下面是一个更复杂的lambda抽象:请注意财务报表表示为远离主体的变量0 lambda应用于远离主体的参数1 lambda从身体上。

_:quoteTerm(λ(a:ℕ)(f:ℕ→ℕ)→f a)≡lam可见(abs“a”(lam可见(abs“f”(var 0(arg(arg-info可见相关)(var 1[])([])))_=回流

这相当混乱,让我们引入一些语法糖,使其更具可读性。

infixr 5λ𝓋_λ𝒽_↦_λ𝓋_↦_λ𝒽_:字符串→术语→术语λ𝓋x↦车身=lam可见(abs x车身)λ𝒽x↦车身=隐藏的lam(abs x车身)

现在,前面的示例对眼睛来说更容易:

_:报价项(λ(a:ℕ)(f:\8469;→\8469;)→f a)≡λ“a”λ“f”变量0[(变量1[])]_=回流

使用这种美味的糖,让我们从很多方面来看这个常数函数。

_:{AB:Set}→quoteTerm(λ(A:A)(B:B)→A)≡λ“a”(λ“b”变量1[])_=回流_:quoteTerm(λ{A B:集合}(A:A)(_:B)→A)≡(λ“A”(λB)_=回流常数:{AB:Set}→A→B→A常数a_=a_:quoteTerm const≡def(引号const)[]_=回流

最后,这里是一个部分的示例。

_:quoteTerm(_≡“b”)≡λ𝓋“部分”↦(定义(引用_≡_)(𝒽𝓇𝒶(def(引用Level.zero)[])𝒽𝓇𝒶 (def(引号字符串)[])𝓋𝓇𝒶 (var 0[])|𝓋𝓇𝒶 (lit(字符串“b”))_=回流

使用Typechecking Monad进行元编程总费用

这个总费用monad为Agda的类型检查器提供了一个接口。

假设TC:{a}→设置a→设置areturnTC:∀{a}{a:设置a}→a→TC abindTC:∀{ab}{a:集合a}{b:集合b}→TC a→(a→TC b)→TC b

为了使用-我们需要在范围内有以下定义。

_>>=_:{ab}{a:集合a}{b:集合b}→TC a→(a→TC b)→TC b_>>=_=绑定TC_>>_:∀{ab}{a:集合a}{b:集合b}→TC a→TC b→TC b_>>_=λp q→p>>=(λ_→q)

的原语总费用可以在上看到文档页面;以下是一些值得注意的我们可以使用的。其他原语包括对当前上下文的支持,类型错误和元变量。

假设{-拿下你所拥有的,并努力使其符合当前目标。-}统一:(有:学期)(目标:学期)→TC{-尝试第一次计算,如果由于类型错误而崩溃,请尝试第二次。-}catchTC:∀{a}{a:设置a}→TC a→TC a{-推断给定术语的类型。-}inferType:术语→TC类型{-根据给定类型检查术语。这可能会解析隐式参数在术语中,因此将返回一个新的细化术语。可用于创建新的元变量:newMeta t=checkType未知t-}checkType:术语→类型→TC术语{-计算术语的标准形式。-}正常化:术语→TC术语{-引用一个值,返回相应的Term.-}quoteTC:∀{a}{a:设置a}→a→TC术语{-取消对术语的引用,返回相应的值。-}unquoteTC:{a}{a:Set a}→Term→TC a{-创建新名称。-}freshName:字符串→TC名称{-声明给定类型的新函数。必须定义该函数稍后使用“defineFun”。采用参数名称以允许声明实例和不相关的函数。Arg的可见性不能隐藏。-}declareDef:参数名称→类型→TC⊤{-定义已声明的函数。该函数可能已使用“declareDef”或在程序中具有显式类型签名。-}defineFun:Name→List子句→TC⊤{-获取已定义名称的类型。替换“primNameType”。-}getType:Name→TC类型{-获取已定义名称的定义。替换“primNameDefinition”。-}getDefinition:名称→TC定义{-更改inferType、checkType、quoteTC、getContext的行为使结果正常化(或不正常化)。默认行为为否归一化。-}标准化:{a}{a:Seta}→Bool→TCA→TCA

总费用计算或“元程序”可以通过将其声明为宏或无报价。让我们从前者开始。

取消引用—创建新功能和类型

回忆一下我们的RGB(RGB)示例类型是一个简单的枚举,包含红色、绿色、蓝色.考虑单例类型:

数据IsRed:RGB→设置位置是的:是红色

姓名红色完全确定此数据类型;所以让我们尝试生成它机械地。不幸的是,据我所知,目前没有办法取消报价数据声明。因此,我们将满足以下条件同构函数式:

IsRed:RGB→设置IsRed x=x≡红色

首先,为了可读性,让我们引用相关部分。

“▽₀”:Arg术语ℓ₀” = 𝒽𝓇𝒶 (定义(引用级别为零)[])“RGB”:参数术语“RGB”=(定义(引号RGB)[])“红色”:Arg术语“红色”=(引用红色)

前两个定义几乎相同,最好是机械地推导它们…

无论如何,我们使用unquoteDecl(取消报价)关键字,它允许我们获取名称值,以色列红色.然后引用所需的类型,声明该类型的函数,然后定义它使用提供的名称.

未报价Decl IsRed=do ty←quoteTC(RGB→设置)声明Def(IsRed)tydefineFun IsRed[子句[(var“x”)](def(quote _≡_)(“▽₀”“RGB”“Red”𝓇𝓋0[][])]

让我们试试我们新声明的类型。

red-is-a-溶液:IsRed redred-is-a-solution=回流绿色即非解决方案:(IsRed-green)绿色-非-解决方案=λ()red-is是唯一的解决方案:∀{c}→IsRed c→c≡redred-is-the-only-solution ref=refl

使用时出现了一个主要问题非引用定义完全是这样:我们不能使用漏洞逐步完善我们的程序?,因为那样会导致未解决的元变量。相反,我们将这个过程分为两个阶段:编程阶段,然后是非报价阶段。

{-定义阶段,我们可以在形成这个程序时使用“?”。-}define Is:名称→名称→TC⊤define-Is Is-name qcolour=defineFun Is-name[条款[(var“x”)]声明-Is:名称→名称→TC⊤声明-是Is-name qcolour=确实让η=is-nameτ←quoteTC(RGB→设置)declareDef(η)τdefineFun is名称[条款[(var“x”)](def(quote _≡_)(“У₀”(“RGB”){-取消引用阶段-}IsRed′:RGB→设置unquoteDef IsRed′=define-Is IsRed’(引用红色){-尝试-}_:IsRed′Red_=回流

请注意,如果使用“unquoteDef”,则必须提供类型签名。我们这样做只是为了说明;下一个代码块通过以下方式避免了这种冗余使用“unquoteDecl”。

上述通用方法也适用于其他数据构造函数:

unquoteDecl IsBlue=声明-IsBlue(引用蓝色)unquoteDecl IsGreen=声明IsGreen(引用绿色){-示例用法-}分离rgb:{c}→(IsBlue c×IsGreen c)分离rgb(ref,())

下一个自然步骤是避免手动调用声明-Is对于每个构造函数。不幸的是,由于某些原因,似乎无法访问新名称。😢

例如,您可能认为以下内容会产生一个函数命名绅士-反思-身份然而,它不在范围内。我甚至尝试将定义提取到它自己的文件中,但没有成功。

未引用的Decl{-标识-}=do{-让η=恒等式-}η←新生姓名“身份”τ←quoteTC({A:Set}→A→A)declareDef(η)τdefineFunη[子句[(var“x”)](var 0[])]{-“身份”不在范围内!?_:∀{x:ℕ}→恒等式x≡x_=回流-}

练习:

  1. 注释掉新鲜名称行并将周围的工件取消注释为,以便单元测试通过。

  2. 将其用作模板,取消引用函数随处-0:\→\它始终为0。

  3. 取消引用常量组合符K:{AB:Set}→A→B→A.

    unquoteDecl everywhere-0=做

    _:随处-0 3≡0_=回流

    未报价Decl K=做

    _:K 3“猫”≡3_=回流

奖金:单例类型的证明,例如以色列红色对于所有单例类型来说基本相同结束RGB(RGB)。分两个阶段编写元程序,演示每个单例类型都有一个成员到岸价。,红色是唯一的解决方案从上面。提示:这个问题和前面的问题一样简单。

{-编程阶段}声明唯一:名称→(RGB→设置)→RGB→TC⊤声明独特的S色==做{-取消引用阶段-}unquoteDecl red-unique=declare-unique red-unique IsRed红色unquoteDecl green unique=声明唯一的绿色唯一的IsGreen绿色unquoteDecl blue-unique=声明独特的blue-unique IsBlue blue{测试-}_:∀{c}→IsGreen c→c≡Green_=绿色-unique

Sidequest:避免单调乏味回流证据

喘息时间(•̀ᴗ•و)

查看您的代码库,寻找进行显式模式匹配的函数,例如:

just-Red:RGB→RGBjust-Red红色=红色just-Red绿色=红色just-Red蓝色=红色仅蓝色:RGB→RGBonly-蓝色-蓝色=蓝色仅蓝色_=蓝色

此类函数具有无法证明的属性,除非我们进行模式匹配在参数上他们模式匹配。例如,上述函数是不断地红色需要模式匹配,然后是回流针对每个条款。

just-Red-is常量:∀{c}→just-Red c≡Redjust-Red-is-constant{Red}=参考just-Red-is-constant{Green}=参考just-Red-is-constant{Blue}=参考{-恶心,又一个乏味的证明-}only-Blue-is常量:∀{c}→only-Bloe c≡Blueonly-Blue-is常量{Blue}=ref只有蓝色是常量{红色}=reflonly-Blue-is-constant{Green}=ref

在这种情况下,我们可以对一般设计决策进行编码---模式匹配和产量refl然后将模式应用于每个用例。

以下是模式:

构造函数:定义→列表名称构造函数(数据类型pars-cs)=cs构造函数_=[]按refls:名称→术语→TC⊤by-refls名称thm-you-hope-is-provable-by-refls=let mk-cls:名称→子句mk-cls qcolour=子句[(con qcolour[])](con(引号ref)[])在里面do让η=标称δ←getDefinition(引用RGB)let子句=List.map mk-cls(构造函数δ)declareDef(η)thm-you-hope-is-provable-by-refs定义Funη子句

这里有一个用例。

_:∀{c}→仅红色c≡红色_=不错其中unquoteDecl-nice=by-refls-nice(quoteTerm(∀{c}→just-Red c≡Red))

注:

  1. 第一个美好的指的是函数由非上市公司RHS创建。

  2. RHS公司美好的指提供的Name值通过LHS。

  3. 左侧美好的是Name值的声明。

这相当笨拙,因为要证明的定理重复了两次重复是错误的信号!在下一节中,我们使用宏来避免这种重复,以及报价条款关键字。

请注意,我们使用哪里子句,因为在,出于某种原因。

这是证明模式的另一个用例(•Éᴗ•́)و

_:∀{c}→仅蓝色c≡蓝色_=不错其中unquoteDecl-nice=by-refls-nice(quoteTerm∀{c}→仅蓝色c≡蓝色)

一个证明模式,多次调用!超级整洁的东西😁

宏——抽象证明模式

宏是类型的函数τ₀→τ̵→σ→项→TC⊤块。最后一个参数由类型检查器提供,表示宏所在位置的“目标”:一个通常统一它们所拥有的有了目标,使用站点中需要什么。

为什么阻塞?

  • 元程序可以在术语位置运行。

  • 在没有宏块的情况下,我们使用无报价关键字。

  • 自动报价;例如。,如果f:Term→Name→Bool→Term→TC⊤然后是一个应用程序f u v w去糖成unquote(f(quoteTerm u)(引号v)w).

    无语法开销:宏像普通函数一样应用。

宏不能是递归的;而是在然后宏块将宏调用递归函数。

C样式宏

在C语言中,定义宏的方法是#定义luckyNum 1972然后再使用它只是名字luckyNum(幸运数字)。如果没有宏,使用这个无报价关键词:

luckyNum₀:术语→TC⊤luckyNum₀h=统一h(引用条款55)编号:num₀=取消引用luckyNum

相反,我们可以通过将元编程代码放在块。

luckyNum:术语→TC⊤luckyNum h=统一h(quoteTerm 55)编号:ℕnum=幸运数字

与C不同,所有代码片段都必须定义良好。

练习:编写一个宏以始终生成函数中的第一个参数。第二个示例显示了如何使用它访问隐式参数不提:b

第一个:术语→TC⊤第一个目标=myconst:{AB:Set}→A→B→Amyconst=λx→λy→第一个mysum:({x}y:ℕ)→\8469'mysum y=y+第一个

C风格的宏(针对具体引用的术语进行统一)很有帮助学习反思时。例如,定义宏使用那就产生了根据输入的形状不同的字符串&这个练习增加对期限类型。提示:上的模式匹配第一个参数;-)

使用:术语→术语→TC⊤使用=秒{-完全定义,无参数。-}2+2≈4 : 2 + 2 ≡ 42+2≈4=回流_:使用2+2≈4≡“漂亮”_=回流{-'p'有参数。-}_:{xy:ℕ}{p:x≡y}→使用p≡“哇那里”_=回流

枯燥重复的证据不再存在!

假设我们想证明加法、乘法和求幂分别具有正确的单位0、1和1。我们得到了以下几乎相同的结果证据!

+-rid:{n}→n+0≡n+-rid{zero}=回流+-rid{suc n}=cong suc+-rid*-rid:{n}→n*1≡n*-rid{zero}=回流*-rid{suc n}=cong suc*-rid^-rid:{n}→n^1≡n^-rid{zero}=回流^-rid{sucn}=cong-suc^-rid

很明显,这里有一个模式需要抽象,让我们遵从♥‿♥

函数式语言的自然操作过程是尝试一个高阶组合词:

{-“for loops”或“Incluction forℕ”-}foldn:(P:ℕ→Set)(基数:P zero)(ind:∀n→P n→P(sucn))→ ∀(n:ℕ)→P nfoldn P base ind zero=基数foldn P base ind(sucn)=ind n(foldn P base ind n)

现在证明更短了:

_:∀(x:ℕ)→x+0≡x_=foldn_ref(λ_→cong-suc){-这两个和接下来的两个是相同的-}_:∀(x:ℕ)→x*1≡x_=foldn_ref(λ_→cong-suc){-Yup,与前面的证明相同-}_:∀(x:ℕ)→x^1≡x_=foldn_ref(λ_→cong-suc){-无变化,与前面的证明相同-}

不幸的是,我们正在手动复制相同的证据图案.

当你看到重复,复制,知道还有改进的余地!(•̀ᴗ•́)و

不要重复!

可以通过多种方式减少重复,例如,包括类型类或元编程。后者可能需要较少的思考,这是本文的主题,所以让我们这样做😄

练习:按照之前练习的模板,填写以下缺失部分。提示:这与之前的练习难度几乎相同。

make-rid:(让A=ℕ)(_ \8469]:A→A→A)(e:A)→名称→TC⊤使rid为标称=做_:∀{x:ℕ}→x+0≡x_=nice where unquoteDecl nice=make-rid _+_ 0 nice

这里有太多的语法开销,让我们使用宏来代替。

_普通的has-rid_:(设A=ℕ)(_ \8469]:A→A→A)(e:A)→项→TC_平凡的目标=doτ←quoteTC(λ(x:ℕ)→x𔫒e≡x)统一目标(def(quote foldn){-使用foldn-}(𝓋𝓇𝒶τ{-类型P-}▪𝓇𝒶(con(引号ref)[]){-基本情况-}●𝓋𝓇𝒶(λ\120011'“_”↦quoteTerm(cong-suc)){-感应步长-}∷ []))

现在证明的重复性最小证明模式是只写的一旦:

_:∀(x:ℕ)→x+0≡x_=_+_微不足道的-has-rid 0_:∀(x:ℕ)→x*1≡x_=_*_普通的手-里德1_:∀(x:ℕ)→x*1≡x_=_^_平凡的手-里德1

注意,我们可以查看目标的类型,找到操作符_⊕_和单位;不需要传入。稍后我们将了解如何进入目标类型并将其取出进行操作(•̀ᴗ•و)

如果我们可以定义宏而不使用折叠;我想不出怎么做。😧

在将模式抽象为宏之前,有几个实例很有用预先确定模式。抽象时,人们可能想比较一下我们的思维方式而不是阿格达的想法。例如,您可能已经注意到在前面的宏,Agda将表达式归一化例如n+0进入之内suc(n+0)通过调用定义属于_+_。我们可以使用引用目标____语法:

+-rid′:∀{n}→n+0≡n+-rid′{zero}=回流+-rid′{suc n}=报价目标e insuc-n:术语suc-n=con(引用suc)[(var 0[])]lhs:期限lhs=def(引号+)(suc-n){-检查我们对目标“e”的理解。-}_:e≡def(引号_≡_)(quoteTerm Level.zero)●lhs_=回流{-它看起来正常化了什么。-}_:quoteTerm(例如(n+0)≡n)≡unquoteλ目标→(do g←归一化目标;统一g目标)_=回流在里面cong-suc+-rid′

比方说,用宏替换最后一行真的很好归纳.不幸的是,为此我需要获得这个名字+-rid′就我而言在当前的反射机制下,tell是不可能的。

我们的第一个实证策略

当我们有证据时p:x≡y不得不写信真讨厌符号p来证明y≡x我们必须记住哪个“方向”第页.让我们减轻这么小的负担,然后使用这里的工具可以在以后减轻更大的负担;即重写子表达式。

鉴于p:x≡y,我们不能简单地屈服定义(引号-符号)[自从sym(对称)事实上接受四个论点&当我们引用时进行比较_≡_早期的。相反,我们推断第页比如说,引用项(_≡_{У}{A}xy)。然后我们可以正确地提供所有必需的参数。

≡-type-info:Term→TC(Arg Term×Arg Terms×Term×Term)≡-type-info(def(quote _≡_)(\120001;\119983;arg _ l arg _r[]))=返回TC(𝓁,𝒯,l,r)≡-type-info _=typeError[strErr“术语不是≡类型。”]

如果后来我们决定不需要x≡y,但不是x≡y.在这种情况下,原始证据第页就足够了。而不是重写我们的证明词如果对称应用程序失败,宏可以尝试提供它。

{-尝试计算的语法糖,如果失败,则尝试另一个-}try-fun:∀{a}{a:设置a}→TC a→TC atry-fun=捕获TC语法try-fun t f=try t or else f

有了设置,我们现在可以形成宏:

应用₁:术语→术语→TC⊤apply₁p目标=尝试(doτ←inferType p𝓁 , 𝒯 , l,r←≡-类型信息τ统一目标(def(quote sym)(𝓁 ∷ 𝒯 ∷ 𝒽𝓇𝒶 l|𝒽𝓇r|𝓋p|[]))或以else统一目标p

例如:

假设𝓍𝓎:ℕ假设𝓆:𝓍+2 lect𝓎{-相同的证明产生两个定理_ : 𝓎 ≡ 𝓍 + 2_=应用_ : 𝓍 + 2 ≡ 𝓎_=应用

让我们有能力检查产生证据。

{-键入批注-}语法有A A=A∶A有:∀(A:Set)(A:A)→A有A A=A

我们正在使用通过输入获得的“ghost colon”\:.

让我们在任意类型上尝试一下:

woah:{A:Set}(xy:A)→x≡y→(y≡x)×(x≡y)woah x y p=应用p,应用p其中--每次调用都会生成不同的证明,实际上:第一个pf:(应用₁p∶(y≡x))≡symp第一个pf=refl第二个pf:(应用₁p∶(x≡y))≡p第二个pf=refl

有趣的是,在非≡项上,应用₁只是一个禁忌。为什么会这样?

_:∀{A:Set}{x:A}→应用₁x≡x_=回流_:应用“哈”选择“哈”_=回流

练习:例如,当我们手动形成一个调用对称性的证明时,我们只需写下,符号p并推断出隐含参数。我们实际上可以在这里做同样的事情!我们在上面有点不诚实。👂重写应用₁,称之为应用,以便~try块是一个单独的,没有括号的,统一呼叫。

练习:扩展上一个宏,以便我们可以证明形式的语句x≡x不管发生什么第页证明。美学提示:try或else_在这种情况下根本不需要括号。

应用三:术语→术语→TC⊤应用三p目标=____yummah:{A:集}{xy:A}(p:x Selecty)→x Selecty×y Selectx×y Selectyyummah p=应用三p,应用三p和应用三p

练习:编写以下看似愚蠢的宏。提示:您不能使用≡-type-info方法,而必须调用获取类型事先。

≡-type-info′:名称→TC(参数术语×参数术语×术语×术语)≡-type-info′=↓sumSides:名称→术语→TC⊤sumSides n目标=_:sumSides𝓆≡𝓍+2+𝓎_=回流

练习:编写两个宏,左边正确的,因此sumSides q≡left q+right q,其中q个是一个已知的名称。这两个宏提供了≡-项。

编写宏的启发

我发现以下逐步细化方法在构建宏。以证明为中心的测试驱动开发——

  1. 编写no-op宏:mymacro p目标=统一p目标.
  2. 编写测试用例mymacro p≡p.
  3. 感觉不错,你成功了。
  4. 稍微改变一下测试,使其更接近你的目标。
  5. 测试现在中断了,去修复它。
  6. 转至步骤3。

例如,假设我们希望考虑证据第页形式表达式的h x≡y我们的宏旨在获得函数小时我们的工作如下:

  1. 假设x、 y、h、p所以这个问题摆得很好。

  2. 使用上述方法形成no-op宏。

  3. 将测试细化为mymacro p≡λe→0并细化宏。

  4. 将测试细化为mymacro p≡λe→e并细化宏。

  5. 最终通过了预期的测试mymacro p≡λe→he然后eta降低。

    在此过程中,返回一串的名称小时或将测试重写为_≡_{零级}{ℕ→\8469»}(mymacro p)≡.这可以提供有关如何修复或继续宏构造的见解。

  6. 扔掉这些假设,一次一个,让它们在测试中成为论据;每次都要细化宏,以便在删除每个假设时测试继续通过。每个假设移除都可能需要更改现有的辅助功能。

  7. 我们考虑了函数应用,然后是变量函数,最后是考虑构造函数。确保针对此特定问题的测试涵盖所有这些内容。

练习:执行此操作以生成上述示例宏,调用它≡-头部。帮助您顺便说一下,这里有一个有用的函数:

{-如果我们有“f$args”,则返回“f”。-}$-头:期限→期限$-head(var v args)=var v[]$-head(con c args)=con c[]$-head(def f args)=def f[]$-head(pat-lam cs args)=pat-lam cs[]$-水头t=t

由于能够获得应用于命题等式的函数,我们现在可以从x≡y足以证明f x选择f y.我们从期望的目标开始,并使用概述的逐步细化方法提前到达:

应用₄:术语→术语→TC⊤apply₄p目标=try(doτ←inferType目标_,_,l,r←≡-类型信息τ统一目标(def(quote cong)($-head l)奥尔斯统一目标p_:∀{xy:ℕ}{f:\8469;→\8469»}(p:x选择y)→fx选择fy_=λp→应用₄p_:∀{xy:ℕ}{fg:\8469;→\8469»}(p:x选择y)x≡y--→fx≡gy{-“apply₄p”现在有一个统一错误^_^-}_=λp→应用₄p

在子表达式的深处怎么样?

考虑一下,

例如X+(X*例如X+例如X)Select⟨cong(λit→suc X+it)(+-suc_ _)⟩suc X+suc(X*suc X+X)

我们能找到吗(λit→suc X+it)机械;-)

使用前面概述的相同优化方法,我们从以下开始然后慢慢地工作代码,一次一段,用一个unquote(统一(quoteTerm…此处为工作代码…)).然后我们推报价条款尽可能深入并构造helper函数这种交易发生了。

打开导入数据。自然属性{-+-suc:∀mn→m+sucn≡suc(m+n)-}测试₀:∀{mnk:ℕ}→k+(m+sucn)≡k+suc(m+n)测试{m}{n}{k}=cong(k+)(+-sucmn)

让我们按照前面提到的方法,从一些假设开始。

假设𝒳:ℕ假设𝒢:suc𝒳+𝒮𝒳 : Arg术语𝒮𝒳 = 𝓋𝓇𝒶 (con(quote suc)[引用术语])𝒢ˡ𝒢ʳ:术语𝒢ˡ=定义(引号_+_)(𝒮𝒳 ∷ 𝓋𝓇𝒶 (def(引号_+_)(引号_*_)(引用术语𝒳) ∷ 𝒮𝒳 ∷ [])) ∷ 𝒮𝒳 ∷ [])) ∷ [])𝒢ʳ=定义(引号_+_)(𝒮𝒳 ∷ 𝓋𝓇𝒶 (con(引号suc)[(def(引号_+_)(def)(引号_*_)(引用术语𝒳) ∷ 𝒮𝒳 ∷ [])(引用术语)

似乎𝒢的左右两侧在def(引号_+_)(𝒮𝒳[]):我们检查引用运算符的相等性,_+_,然后递归检查参数。由此产生了以下朴素算法:

{-肯定应该在标准库中-}⌊_⌋:∀{a}{a:设置a}→Dec a→Bool⌊yes p⌋=真⌊否⌋=假进口Agda。内置。作为内置的反射_$-≟_:术语→术语→布尔con c args$-≟con c′args′=Builtin.primQNameEquality c′def f args$-≟def f′args′=内置.primQNameEquality f f′var x args$-≟var x′args′=⌊x Nat.\8799;x′⌋_$-≟_=错误{-只获取头和相同数量的常用参数,不深入任何地方。:'(-}中缀5_⊓_{-#TERMINATING#-}{-通过添加燃料(con c args)≔1+长度args来解决此问题-}_⊓_:术语→术语→术语l⊓r和l$-≟r|l|r…|false|x|y=未知…|true|var f args | var f′args′=var f(List.zipWith(λ{(arg i!!t)(arg j!!s)→arg i!!(t⊓s)})args args′)…|true|con f args | con f′args′=con f(List.zipWith(λ{(arg i!!t)(arg j!!s)→arg i!!(t⊓s)})args args′)…|true|def f args | def f′args′=def f(List.zipWith(λ{(arg i!!t)(arg j!!s)→arg i!!(t⊓s)})args args′)…|true|ll|_=ll{左偏;使用“unknown”不能确保幂等性。-}

这些尸体的名字包括!!,这是为了表明改进的位置。事实上,这种天真的算法忽略了参数的可见性和相关性,这远非理想。

这真是太棒了!😂

_:选择定义(引号_+_)(𝒮𝒳 ∷ 𝓋𝓇𝒶 未知©[])_=回流{使用参数函数𝒶和参数编号X-}进行测试_:{X:ℕ}{\119990;:\8469'→\8469]}设gl=quoteTerm(X+(X*X+X))gr=报价条款(X+X(X*X+X))在gl⊓gr≡def(引号+)(变量0[变量1[])])中_=回流

这个未知的条款远非理想&我们应该用章节取代;即无名lambda。我用朴素的算法从包含“未知”的术语中获得一个部分,如下所示:

  1. 更换间隔未知的使用De Bruijn索引。
  2. 然后,找出有多少未知数,并为每个未知数在前面贴上一只无名的羔羊。
    • 在前面粘贴一个lambda会破坏现有的De Bruijn索引,因此为每个lambda增加这些索引。

这里显然效率低下,但我的目标不是效率,只是在某种程度上可信。

{-“unknown”指向一个变量,即De Bruijn索引-}unknown-elim:ℕ→列表(Arg Term)→列表(Arg Term)未知-elim n[]=[]unknown-elim n(arg i unknown|xs)=arg i(var n〔〕)|unknown-elim(n+1)xs未知-elim n(arg i(var x args)xs)=arg i未知elim n(arg i xŞxs)=arg i xŞ未知elim n xs{-基本上我们想要:身体(未知ᵢ)⇒λ_→身体(var 0)然而,现在“body”中的所有“var 0”引用引用了错误的参数;他们现在指的是“比以前多了一个lambda”。-}未知计数:列表(参数项)→ℕ未知计数[]=0未知计数(arg i未知xs)=1+未知计数xs未知计数(arg i|xs)=未知计数xs未知-λ:ℕ→术语→术语未知-λ零体=体未知-λ(sucn)体=未知-λs n(λ𝓋“截面”↦体){-将“未知”替换为节-}补丁:Term→Term修补它@(def f args)=未知-λ(未知-计数args)(def f(未知-elim 0 args))修补它@(var f args)=未知-λ(未知-计数args)(var f(未知-elim 0 args))修补它@(con f args)=未知-λ(unknown-count args)(con f(unknowsn-elim 0 args))补丁t=t

推介会,_⊓_,还有这个补丁合并为一个宏:

脊柱:术语→术语→TC⊤spine p目标=doτ←推断类型p_,_,l,r←≡-类型信息τ统一目标(补丁(l⊓r))

预期的测试通过了,真高兴😂

_:spine𝒢≡suc𝒳+__=回流模块测试假定功能,其中假设:→假设_𝒷_ : ℕ → ℕ → ℕ假设𝓰 : 𝒶 𝒳  𝒷  𝒳  ≡  𝒶 𝒳  𝒷  𝒶 𝓍_:脊柱𝓰≡(𝒶𝒳𝃏_)_=回流_:{X:ℕ}{G:suc X+(X*suc X+suc X)select suc X+/suc(X*suc X+X)}引用项G lect var 0[]_=回流

以下各项的测试Select-头部仍然使用脊椎因此可以认为是一种概括;-)

现在,原始问题作为一个宏处理:

apply:术语→术语→TC⊤应用p孔=doτ←inferType孔_,_,l,r←≡-类型信息τ统一孔((def(引号cong)(补丁(l⊓r))

奇怪的是,为什么在下面的测试中我们不能简单地使用+-例如__?

_:suc𝒳+(\119987;*suc𝃋·+suc߃)select suc \196987;+suc_=应用(+-suc(𝒳*suc \119987;)测试:{mnk:}→k+(m+sucn)≡k+suc(m+n)test{m}{n}{k}=apply(+-sucmn)

这真是太棒了^_^

关于

对Agda中反射的缓慢介绍---战术!

话题

资源

星星

观察者

叉子

发布

未发布任何版本

包装

未发布包

语言文字