滥用代数数据类型的代数-为什么这样做有效?


289

对于具有数学背景的人来说,代数数据类型的“代数”表达式非常有启发性。让我尝试解释一下我的意思。

定义了基本类型

  • 产品
  • 联盟 +
  • 辛格尔顿 X
  • 单元 1

并使用简写X•X2XX+X等等,我们就可以定义,例如链表代数表达式

data List a = Nil | Cons a (List a)L = 1 + X • L

和二叉树:

data Tree a = Nil | Branch a (Tree a) (Tree a)T = 1 + X • T²

现在,我作为数学家的本能是对这些表达式不屑一顾,并尝试解决LT。我可以通过重复替换来做到这一点,但是似乎很容易滥用这种表示并假装可以随意重新排列它。例如,对于链接列表:

L = 1 + X • L

(1 - X) • L = 1

L = 1 / (1 - X) = 1 + X + X² + X³ + ...

在这里,我1 / (1 - X)以完全不合理的方式使用的幂级数展开来得出有趣的结果,即L类型为Nil,或者包含1个元素,或者包含2个元素,或者3个,等等。

如果我们对二叉树进行处理,它将变得更加有趣:

T = 1 + X • T²

X • T² - T + 1 = 0

T = (1 - √(1 - 4 • X)) / (2 • X)

T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...

再次使用幂级数展开(与Wolfram Alpha一起完成)。这表示了一个非显而易见的事实(对我而言),即只有一棵二元树包含1个元素,2个二元树包含两个元素(第二个元素可以在左边或右边的分支),5个二元树以及三个元素等等。 。

所以我的问题是-我在这里做什么?这些运算似乎是没有道理的(代数数据类型的平方根到底是什么?),但它们会导致有意义的结果。两个代数数据类型的商在计算机科学中是否有任何意义,还是仅仅是符号欺骗?

而且,也许更有趣的是,可以扩展这些想法吗?是否存在类型代数的理论,例如允许类型上的任意函数,或者类型是否需要幂级数表示?如果可以定义一类函数,那么函数的组合是否有意义?


19
您可能会发现这个有趣/相关的:blog.lab49.com/archives/3011
shang

4
如果它在每个节点中都存储数据,则不会。它看起来像Branch x (Branch y Nil Nil) Nil还是看起来像Branch x Nil (Branch y Nil Nil)
克里斯·泰勒

4
@nlucaroni:bottom是一个值,而不是类型。真正的零类型将不具有该类型的任何值,除非您忽略底部,否则在Haskell中是不可能的。如果您确实考虑了底部值,那么仅包含底部的类型将成为单位类型,这在大多数情况下是无济于事的,许多其他内容也会中断。
CA McCann

3
我同意这是Haskell的普遍做法,但仍然很愚蠢。即,这意味着我们在逻辑和类型理论中使用“底部”的方式有所不同,而后者对我不利。从纯代码中看起来并不相同:“解决尴尬小队”清楚地表明,Haskell的语义包含大量“坏值”,这些值永远循环并引发异常显然不同。用一个替换另一个是无效的方程式推理。Haskell有用于描述这些坏值的词汇undefinedthrow等等。我们应该使用它。
菲利普·JF

17
这个问题

Answers:


138

免责声明:当您考虑⊥时,其中的许多功能实际上并不太正确,因此,为了简单起见,我将公然地忽略它。

一些初步的观点:

  • 请注意,此处的“联合”可能不是A + B的最佳术语-具体来说,这是两种类型不相交的并集,因为即使它们的类型相同,也可以区分双方。就其价值而言,更常见的术语只是“求和类型”。

  • 实际上,单例类型是所有单位类型。它们在代数运算下的行为相同,更重要的是,仍然保留了当前存在的信息量。

  • 您可能还需要零类型。Haskell提供了Void。没有类型为零的值,就像有一个类型为1的值一样。

这里仍然缺少一项重要的操作,但我稍后会再讲。

您可能已经注意到,Haskell倾向于借鉴类别理论的概念,并且以上所有内容都具有非常简单的解释,例如:

  • 给定Hask中的对象A和B ,其乘积A×B是唯一的(直到同构)类型,允许两个投影fst:A×B→A和snd:A×B→B,其中给定任何类型C和函数f:C→A,g:C→B您可以定义配对f &&& g:C→A×B使得fst∘(f &&& g) = fg也是如此。参数化可自动保证通用属性,而我不太喜欢的名称选择应该可以为您带来灵感。该(&&&)运营商定义的Control.Arrow,顺便说一句。

  • 上面的对偶是注射有inl:A→A + B和inr:B→A + B 的副产品A + B ,其中给定任何类型C和函数f:A→C,g:B→C,您可以定义配对| || g:A + B→C使得明显的等价成立。同样,参数化可自动保证大多数棘手的部分。在这种情况下,标准注射很简单LeftRight并且配对是函数either

乘积和总和类型的许多属性可以从上面得出。请注意,任何单例类型都是的终端对象 Hask任何空类型都是初始对象。

返回到上述缺失操作,在笛卡尔封闭类别中,您具有与类别箭头对应的指数对象。我们的箭头是函数,我们的对象是具有kind的类型*,并且在对类型进行代数运算的情况下,该类型A -> B的确确实表现为B A。如果尚不清楚为什么应该保留该类型,请考虑type Bool -> A。在只有两个可能的输入的情况下,该类型的函数同构为两个类型的值A,即(A, A)。因为Maybe Bool -> A我们有三个可能的输入,依此类推。另外,注意观察,如果我们改写上述copairing定义中使用代数符号,我们得到了认同Ç 一个 ×C B = CA + B

至于为什么这一切都有意义-特别是为什么您使用幂级数展开是合理的-请注意,上面的大部分内容依次指代一种类型的“居民”(即具有该类型的不同值)证明代数行为。明确表示该观点:

  • 产品类型(A, B)从表示各值AB,独立地服用。因此对于任何固定值a :: A(A, B)每个居民都有一个类型值B。当然,这是笛卡尔乘积,并且乘积类型的居民数量是因素的居民数量的乘积。

  • sum类型Either A B表示来自A或的值B,并区分左分支和右分支。如前所述,这是一个不相交的联合,总和类型的居民数是被求和者的居民数之和。

  • 指数类型B -> A表示从type B的值到type的值的映射A。对于任何固定参数b :: BA可以为其分配任何值;类型值为B -> A每个输入选择一个这样的映射,这等效于AB居住者一样多的副本的乘积,因此就是幂。

虽然一开始很想将类型视为集合,但实际上在这种情况下效果并不理想-我们具有不相交的并集而​​不是标准的集合并集,对交集或许多其他集合操作没有明显的解释,我们通常不关心集合成员身份(将其留给类型检查器)。

另一方面,上面的结构花费大量时间来讨论对居民的计数,在这里枚举类型的可能值是一个有用的概念。很快,我们就得到了枚举组合,并且,如果您查阅链接的Wikipedia文章,您会发现其做的第一件事就是通过与乘积和总和类型完全相同的含义来定义“对”和“联合”。生成函数,然后使用与您完全相同的技术对与Haskell列表相同的“序列”执行相同的操作。


编辑:哦,这是我认为可以证明这一点的快速好处。您在评论中提到,对于树类型,T = 1 + T^2您可以派生identity T^6 = 1,这显然是错误的。但是,T^7 = T 确实成立了,可以直接在树和七元组的树之间构造双射,参见。安德烈亚斯·布拉斯(Andreas Blass)的《七棵树合而为一》

编辑×2:关于其他答案中提到的“类型的导数”结构,您可能还喜欢同一作者的这篇论文,该论文进一步基于该思想,包括除法概念和其他有趣的概念。


3
这是一个很好的解释,特别是作为一个出发点到之类的东西strictlypositive.org/diff.pdf
acfoltzer

26
@acfoltzer:谢谢!:]是的,那是一篇很好的论文,提出了这些想法。您知道,我认为我在SO上的总声誉中至少有5%可以归因于““帮助人们了解Conor McBride的论文之一”……
CA McCann

45

二叉树由T=1+XT^2类型半环中的方程式定义。通过施工,T=(1-sqrt(1-4X))/(2X)由复数半环中的相同方程式定义。因此,鉴于我们要在同一类代数结构中求解相同的方程式,因此看到一些相似之处并不奇怪。

问题在于,当我们在复数半环中考虑多项式时,通常会使用一个事实,即复数形成一个环甚至一个域,因此我们发现自己使用了不适用于半环的减法运算。但是,如果我们有一条规则允许我们从等式的两边都抵消,我们通常可以消除自变量的减法。Fiore和Leinster证明了这种情况,表明许多关于环的论点可以转移到半环上。

这意味着您对环的许多数学知识都可以可靠地转换为类型。结果,一些涉及复数或幂级数的争论(在形式幂级数的环中)可以完全严格地延续到类型上。

但是,故事的意义远不止于此。通过证明两个幂级数相等来证明两种类型相等(例如)是一回事。但是,您也可以通过检查幂级数中的术语来推断有关类型的信息。我不确定这里的正式声明应该是什么。(我建议布伦特Yorgey的纸张组合品种的一些工作内容紧密相关,但种一样的类型。)

我完全感到震惊的是,您发现的内容可以扩展到微积分。关于微积分的定理可以转移到类型的半环上。实际上,即使有关有限差分的论点也可以转移,并且您发现数值分析的经典定理在类型论中也有解释。

玩得开心!


这种差异化/单孔上下文的东西非常酷。让我们看看我是否直率。具有代数表示的对P = X^2具有导数dP = X + XEither对的单孔上下文也是如此。太酷了。我们也可以“整合” Either以获得一对。但是,如果我们尝试“集成” Maybe(与type M = 1 + X),那么我们需要拥有\int M = X + X^2 / 2一个荒谬的(一半是type?),这是否意味着这Maybe不是任何其他type的单孔上下文?
克里斯·泰勒

6
@ChrisTaylor:单孔上下文保留有关产品内部位置的信息,即,(A,A)其中有一个孔A的位置可以告诉您孔位于哪一侧。一个A人没有可填补的明显漏洞,这就是为什么您不能“整合”它。在这种情况下,丢失信息的类型当然是2
CA McCann

我已经写过关于如何理解类型的X^2/2 博客
sigfpe 2012年

@ user207442,您是否还对一棵树和七棵树之间的双射做了一些处理?我在答案中链接了一篇关于该主题的论文,但我发誓我记得自己在您的博客上第一次读到它。
CA McCann

1
@ChrisTaylor有限(实际上是“分”)的差异有这样的:strictlypositive.org/CJ.pdf但在这一点上康纳尔没有意识到,他描述的差异。我写了这篇文章,尽管可能有些棘手:blog.sigfpe.com/2010/08/…我会写一篇论文,但是我不太擅长完成它们。
sigfpe 2012年

22

您似乎正在做的只是扩展递归关系。

L = 1 + X  L
L = 1 + X  (1 + X  (1 + X  (1 + X  ...)))
  = 1 + X + X^2 + X^3 + X^4 ...

T = 1 + X  T^2
L = 1 + X  (1 + X  (1 + X  (1 + X  ...^2)^2)^2)^2
  = 1 + X + 2  X^2 + 5  X^3 + 14  X^4 + ...

由于类型操作的规则与算术操作的规则一样,因此可以使用代数方法来帮助您弄清楚如何扩展递归关系(因为它不明显)。


1
“因为类型操作的规则就像算术规则一样工作……” -但是,它们不是。没有类型减法的概念,更不用说除法和平方根了。所以我想我的问题是:什么时候可以从假设X是实数的元素的代数运算转变为关于类型的真实陈述,而且,对应关系(n度项的系数)<=>(数持有n元素的类型)来自哪里?
克里斯·泰勒

1
例如,T = 1 + T^2我可以从Tree()的表达式中得出T^6 = 1(即,解决方案x^2 - x + 1 = 0是unity的第六根),但是由六个二叉树组成的乘积类型等同于unit 显然不是正确的()
克里斯·泰勒

3
@ChrisTaylor,但也有一些是发生的事情,因为之间的同构T^7T。cf. arxiv.org/abs/math/9405205
luqui 2012年

7
@ChrisTaylor,这是要考虑的事情。添加新的代数运算时,希望不要破坏现有运算的属性。如果您可以通过两种不同的方式得出相同的答案,那么他们应该同意。因此,提供有所有任何表示L = 1 + X * L,它最好是同一个,你得到当你扩展系列,通过一致性。否则,您可以向后计算结果,以获得关于实数的错误信息。
luqui 2012年

2
@ChrisTaylor确实存在类型划分的概念,请搜索“商户类型”以获取更多信息。我不知道它是否与多项式除法很好地吻合。碰巧,它确实是不切实际的,但它在那里。
道格·麦克林

18

我没有一个完整的答案,但是这些操作往往会“奏效”。一篇相关的论文可能是Fiore和Leinster的“作为复数的类别的对象” -我在阅读sigfpe的相关主题的博客时碰到了该论文 ; 该博客的其余部分是类似想法的金矿,值得一试!

顺便说一下,您还可以区分数据类型-这将为您提供适合该数据类型的Zipper!


12
拉链诀窍是真棒。我希望我能理解。
spraff

您还可以使用定界连续符在Scheme中制作拉链,这使您可以通用地导出它们。
乔恩·普迪

10

通信过程代数(ACP)处理过程的类似表达。它提供加法和乘法作为选择和顺序的运算符,以及相关的中性元素。基于这些,还有其他构造函数的运算符,例如并行性和中断。参见http://en.wikipedia.org/wiki/Algebra_of_Communicating_Processes。在线上还有一篇名为“过程代数的简要历史”的论文。

我正在使用ACP扩展编程语言。去年四月,我在2012年Scala Days上发表了一篇研究论文,可在http://code.google.com/p/subscript/获得。

在会议上,我演示了一个调试器,该调试器运行bag的并行递归规范:

袋= A; (袋子&a)

其中A和代表输入和输出操作;分号和与号代表顺序和并行性。观看SkillsMatter上的视频,该视频可从上一个链接访问。

袋子规格更可比

L = 1 + X•L

将会

B = 1 + X&B

ACP使用公理根据选择和顺序定义并行性。请参阅Wikipedia文章。我想知道这个包比喻是什么

L = 1 /(1-X)

ACP样式编程对于文本解析器和GUI控制器非常方便。规格如

searchCommand = clicked(搜索按钮)+键(Enter)

cancelCommand =单击(cancelButton)+键(Escape)

可以通过隐式“单击”和“键”这两个改进来更简洁地写下(例如Scala允许使用的功能)。因此,我们可以这样写:

searchCommand = searchButton + Enter

cancelCommand = cancelButton +退出

现在,右侧包含作为数据而不是过程的操作数。在此级别上,无需知道哪些隐式细化会将这些操作数转换为进程。他们不一定会完善为投入行动;输出动作也将适用,例如在测试机器人的规格中。

流程以这种方式将数据作为伴生。因此,我创造了“项目代数”一词。


6

微积分和麦克劳林系列的类型

这是另一个较小的补充-对级数展开式中的系数为何应该“起作用”的组合见解,特别是着眼于可以使用泰勒-麦克劳林方法从微积分中得出的级数。注意:您为操作列表类型提供的示例系列扩展是Maclaurin系列。

由于其他答案和注释涉及代数类型表达式(求和,乘积和指数)的行为,因此该答案将消除该细节,并将重点放在类型“演算”上。

您可能会注意到逗号在此答案中起了很大的作用。有两个原因:

  • 我们的工作是将一个领域的解释提供给另一领域的实体,以这种方式界定此类外来概念似乎是适当的。
  • 一些概念将可以更严格地形式化,但是形状和思想比细节更重要(并且占用的空间更少)。

Maclaurin系列的定义

函数的Maclaurin系列f : ℝ → ℝ定义为

f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f⁽ⁿ⁾(0)Xⁿ + ...

其中的f⁽ⁿ⁾意思nf

为了能够理解用类型解释的Maclaurin系列,我们需要了解如何在类型上下文中解释三件事:

  • (可能是多个)导数
  • 将功能应用于 0
  • (1/n!)

结果表明,分析得出的这些概念在类型世界中具有合适的对应形式。

“合适的同行”是什么意思?它应该具有同构的风格-如果我们可以在两个方向上都保留真相,则可以将在一种情况下派生的事实转移到另一种情况下。

类型的微积分

那么类型表达式的派生是什么意思呢?事实证明,对于大型且行为良好(“可区分”)类的类型表达式和函子,存在一种自然的操作,其行为表现得足够相似,足以作为一种合适的解释!

为了破坏重点,类似于区分的操作是进行“单孔上下文”。是一个进一步扩展此特定点的好地方,但是单孔上下文(da/dx)的基本概念是它表示x从术语(of a)提取特定类型()的单个子项的结果,保留所有其他信息,包括确定子项目原始位置所需的信息。例如,一种表示列表的单孔上下文的方法是使用两个列表:一个表示提取的列表之前的项目,另一个表示提取的列表之后的项目。

识别此操作的动机来自以下观察。我们写da/dx的意思是类型为type的单孔上下文a的类型x

d1/dx = 0
dx/dx = 1
d(a + b)/dx = da/dx + db/dx
d(a × b)/dx = a × db/dx + b × da/dx
d(g(f(x))/dx = d(g(y))/dy[f(x)/a] × df(x)/dx

在这里,1和分别0代表居住人数恰好为1和正好为零的类型,并且+×照常表示总和和乘积类型。fg用来表示类型函数或类型表达式生成器,并且[f(x)/a]表示用前面的表达式中的f(x)每个替换的操作a

这可以用无点样式编写f',表示类型为function的派生函数f,因此:

(x ↦ 1)' = x ↦ 0
(x ↦ x)' = x ↦ 1
(f + g)' = f' + g'
(f × g)' = f × g' + g × f'
(g ∘ f)' = (g' ∘ f) × f'

这可能是更可取的。

注意,如果我们使用类型和函子的同构类定义导数,则可以使严格性和精确性相等。

现在,我们特别注意到,微积分中与加法,乘法和合成的代数运算有关的规则(通常称为“求和,乘积和链式规则”)完全由“打洞”运算反映出来。此外,在常量表达式中“打个洞”或该术语x本身的基本情况也表现为微分,因此通过归纳,我们对所有代数类型表达式都获得了类似于微分的行为。

现在我们可以解释差异了,n类型表达式的“导数” dⁿe/dxⁿ是什么意思?它是一种表示n场所上下文的类型:这些n术语在用类型术语“填充”时会x产生一个e。还有另一个与“ (1/n!)” 有关的重要观察结果。

类型函子的不变部分:将函数应用于0

0在类型世界中,我们已经有了一个解释:没有成员的空类型。从组合的角度来看,向其应用类型函数是什么意思?更具体地说,假设f是一个类型函数,它f(0)看起来像什么?好吧,我们当然不能访问任何类型的0,因此任何f(x)需要的构造x都不可用。剩下的就是那些在没有它们时可以访问的术语,我们可以称其为类型的“不变”或“恒定”部分。

举一个明显的例子,使用Maybe函子,该函子可以代数表示为x ↦ 1 + x。将其应用于时0,我们得到1 + 0-就像1:唯一可能的值是Nonevalue。对于列表,类似地,我们仅获得与空列表相对应的术语。

当我们带回它并将类型解释f(0)为数字时,可以将其视为无需访问即可获得多少个类型项(对于任何)的计数:即“空”项的数量。f(x)xx

放在一起:对Maclaurin系列的完整解释

恐怕我想不出对类型的适当直接解释(1/n!)

但是,如果考虑f⁽ⁿ⁾(0)到以上所述的类型,我们会发现它可以解释为类型项的n-place上下文的类型f(x),该术语尚未包含 x -,即当我们“整合”它们的n时间时,结果项恰好是 n x s,不多也不少。然后,将类型解释f⁽ⁿ⁾(0)为数字(如的Maclaurin系列的系数f)只是对存在多少此类空位置n上下文的计数。我们快到了!

但是到哪里(1/n!)去呢?检查类型“分化”的过程向我们展示,当多次应用时,它保留提取子项的“顺序”。例如,考虑(x₀, x₁)类型的术语x × x和两次“打孔”的操作。我们得到两个序列

(x₀, x₁)  ↝  (_₀, x₁)  ↝  (_₀, _₁)
(x₀, x₁)  ↝  (x₀, _₀)  ↝  (_₁, _₀)
(where _ represents a 'hole')

即使两者都来自同一术语,因为有2! = 2多种方法可以将两个元素从两个元素中取出,并保持顺序。通常,有一些n!方法可以从中获取n元素n。因此,为了得到其中有一个仿函数类型的配置数量的计数n的元素,我们要算的类型f⁽ⁿ⁾(0)的和鸿沟n!正是因为在麦克劳林级数的系数。

因此,n!事实证明除以本身就是简单的解释。

最后的想法:“递归”的定义和分析性

首先,一些观察:

  • 如果函数f:ℝ→ℝ具有导数,则该导数是唯一的
  • 同样,如果函数f:ℝ→ℝ解析,则它恰好具有一个对应的多项式级数

由于我们具有链式规则,因此如果将类型导数形式化为同构类,则可以使用隐式微分。但是隐式差异不需要像减法或除法这样的外来动作!因此,我们可以使用它来分析递归类型定义。以您的清单为例,我们有

L(X) ≅ 1 + X × L(X)
L'(X) = X × L'(X) + L(X)

然后我们可以评估

L'(0) = L(0) = 1

以获得麦克劳林级数的系数。

但是,由于我们确信这些表达式的确确实是严格的“可微分的”(即使仅是隐式的),并且由于我们具有functions→the的对应关系,其中导数肯定是唯一的,因此即使我们使用'获得值,我们也可以放心。非法”操作,结果有效。

现在,类似地,要使用第二个观测值,由于与函数ℝ→the的对应关系(是否是同态性),我们知道,只要我们满意一个函数具有 Maclaurin级数,就可以在所有这些,上面概述的原理都可以使其严格。

关于您关于功能组合的问题,我想链式规则提供了部分答案。

我不确定这适用于多少个Haskell风格的ADT,但我怀疑有很多(如果不是全部)。我已经发现了这一事实的绝妙证据,但是这个余量太小了,无法遏制它。

现在,当然,这只是解决这里发生的事情的一种方法,可能还有许多其他方法。

摘要:TL; DR

  • 类型“差异化”对应于“ 打孔 ”。
  • 应用函子可以0使我们获得该函子的“空”字样。
  • 因此,麦克劳林幂级数(某种程度上)严格地对应于枚举具有一定数量元素的函子类型的成员数量。
  • 隐式差异使这种功能更加防水。
  • 导数的唯一性和幂级数的唯一性意味着我们可以捏造细节,并且它可以工作。

6

从属类型理论和“任意”类型函数

我对这个问题的第一个答案是概念高而细节低,并反映在子问题“发生了什么?”;这个答案将是相同的,但侧重于子问题“我们能否获得任意类型的函数?”。

sum和product的代数运算的一种扩展是所谓的“大算子”,它表示通常分别写ΣΠ分别表示的序列的sum和积(或更一般地,一个域上的函数的sum和积)。请参阅Sigma符号

所以总和

a + aX + aX² + ...

可能会写

Σ[i  ℕ]aX

例如,哪里a是实数序列。该产品将类似地以Π代替Σ

当从远处看时,这种表达看起来很像;中的“任意”函数X。当然,我们仅限于可表达的级数及其关联的分析功能。这是类型理论表述的候选人吗?绝对!

具有这些表达式的直接表示形式的类型理论类别是“从属”类型理论类别:具有从属类型的理论。自然地,我们有术语依赖于术语,并且在像Haskell这样的语言中具有类型函数和类型量化,术语和类型取决于类型。在从属设置中,我们还根据术语具有类型。Haskell不是依赖类型的语言,尽管可以通过稍微折磨该语言来模拟依赖类型的许多功能。

咖喱霍华德和从属类型

“柯里·霍华德同构”的诞生是因为观察到简单类型的lambda演算的术语和类型判断规则完全与应用于直觉命题逻辑的自然推论(由Gentzen公式化)相对应,其中类型代替了命题,并且术语代替了证明,尽管两者是独立发明/发现的。从那时起,它一直是类型理论家的巨大灵感来源。要考虑的最明显的事情之一是,命题逻辑的这种对应关系是否以及如何扩展到谓词逻辑或高阶逻辑。依赖类型理论最初是从这种探索途径产生的。

有关简单类型的lambda演算的Curry-Howard同构的介绍,请参见此处。例如,如果我们想证明,A ∧ B就必须证明A和证明B;组合证明只是一对证明:每个合取项一个。

自然推论:

Γ  A    Γ  B
Γ  A  B

在简单类型的lambda演算中:

Γ  a : A    Γ  b : B
Γ  (a, b) : A × B

对于和和类型,函数类型以及各种消除规则也存在类似的对应关系。

不可证明的(直觉上是错误的)命题对应于一个无人居住的类型。

考虑到作为逻辑命题的类型的类比,我们可以开始考虑如何在类型世界中对谓词建模。有许多方法在此已正式(见本介绍马丁- LOF的直觉类型论为一种广泛使用的标准),但抽象的方法通常观察到的谓词就像是一个命题,免费长期变量,或者,一种对命题使用术语的功能。如果我们允许类型表达式包含项,则以lambda演算风格进行处理会立即成为一种可能!

仅考虑构造性证明,什么构成证明∀x ∈ X.P(x)?我们可以将其视为证明函数,将术语(x)用作其相应命题(P(x))的证明。因此,类型(命题)的成员(样张)∀x : X.P(x)是“相关的功能”,其每个xX给类型的术语P(x)

∃x ∈ X.P(x)呢 我们需要的任何成员Xx与的证明一起P(x)。因此,类型(命题)的成员(样张)∃x : X.P(x)是“依赖对”:一个杰出的术语xX,与类型的术语一起P(x)

表示法:我将使用

x  X...

有关该类成员的实际陈述X,以及

x : X...

用于类型表达式,对应于类型的通用量化X。同样适用于

组合注意事项:乘积和总和

除了类型与命题的库里-霍华德对应之外,我们还有代数类型与数字和函数的组合对应,这是这个问题的重点。幸运的是,这可以扩展到上面概述的依赖类型!

我将使用模数表示法

|A|

表示类型的“大小” A,以明确指出问题中概述的类型和数字之间的对应关系。注意,这是理论之外的概念。我不认为该语言中需要任何此类运算符。

让我们计算类型的可能(完全减少的,规范的)成员

x : X.P(x)

这是考虑方面相关的功能的类型x类型的X到类型方面P(x)。每个此类函数必须具有的每个项的X输出,并且此输出必须具有特定的类型。那么,对于xin中的每个X,这都会提供|P(x)|“选择”输出。

重点是

|∀x : X.P(x)| = Π[x : X]|P(x)|

如果Xis IO (),那当然没有什么意义,但是适用于代数类型。

同样地,类型术语

x : X.P(x)

是对类型(x, p)p : P(x),所以任何给定的xX我们可以构造适当的一对带中的任何成员P(x),给人|P(x)|“选择”。

因此,

|∃x : X.P(x)| = Σ[x : X]|P(x)|

同样的警告。

这证明了使用符号对理论中依赖类型的通用表示法是合理的 ΠΣ,并且由于上述对应关系,实际上,许多理论模糊了“所有人”和“产品”之间以及“有”和“和”之间的区别。

我们越来越近了!

向量:表示依赖元组

我们现在可以编码数字表达式吗

Σ[n  ℕ]X

作为类型表达式?

不完全的。尽管我们可以像XⁿHaskell 那样非正式地考虑表达式的含义,在哪里X是类型和n自然数,但这是对符号的滥用。这是一个包含数字的类型表达式:显然不是有效的表达式。

另一方面,对于图片中的从属类型,包含数字的类型正好是重点;实际上,从属元组或“向量”是一个非常常见的例子,说明了从属类型如何为诸如列表访问之类的操作提供实用的类型级安全性。向量只是一个列表,以及有关其长度的类型级别信息:正是我们对类型表达式之类的追求Xⁿ

在此回答期间,让

Vec X n

是-type值的长度n向量的X类型。

从技术上讲,n此处是自然数在系统中的表示,而不是实际的自然数。我们可以Nat用Peano风格将自然数()表示为另一个自然数的零(0)或后继(S),对于n ∈ ℕ˻n˼来说,Nat它的意思是表示的术语n。例如,˻3˼S (S (S 0))

那我们有

|Vec X ˻n˼| = |X|ⁿ

对于任何n ∈ ℕ

NAT类型:将ℕ术语提升为类型

现在我们可以编码像

Σ[n  ℕ]X

作为类型。这个特定的表达式将产生一个类型,该类型X与问题中所标识的列表类型同构。(不仅如此,而且从类别理论的角度来看,类型函数-是一个函子-接受X上述类型自然是同构的,与List函子的。)

“任意”函数的最后一个难题是如何编码,

f :   

像这样的表达

Σ[n  ℕ]f(n)X

这样我们就可以将任意系数应用于幂级数。

我们已经了解了代数类型与数字的对应关系,从而使我们能够将类型映射为数字,并将类型函数映射为数值函数。我们也可以走另一条路!-取一个自然数,显然存在一个可定义的代数类型,其中有许多项成员,无论我们是否具有从属类型。我们可以通过归纳轻松地在类型理论之外证明这一点。我们需要的是一种在系统内部将自然数映射为类型的方法。

一个令人愉悦的认识是,一旦有了依赖类型,归纳证明和递归证明就变得极为相似-实际上,它们在许多理论中都是相同的。既然我们可以通过归纳证明存在满足我们需求的类型,那么我们是否应该不能构造它们呢?

有几种方法可以在术语级别表示类型。在这里,我将使用虚构的Haskellish表示法来表示*类型的宇宙,通常将其本身视为从属环境中的类型。1个

同样,注释“- 消除”的方式也至少与依赖类型理论一样多。我将使用Haskellish模式匹配表示法。

我们需要与属性映射,αNat*

n  ℕ.|α ˻n˼| = n.

以下伪定义就足够了。

data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe

α : Nat -> *
α 0 = Zero
α (S n) = Successor  n)

因此,我们看到α镜像的作用反映了后继者的行为S,使其成为同态。Successor是一个类型函数,将一个类型的成员数量“加一”;也就是说,|Successor a| = 1 + |a|对于任何a具有定义大小的对象。

例如α ˻4˼(是α (S (S (S (S 0))))),是

Successor (Successor (Successor (Successor Zero)))

并且这种类型的术语是

Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))

给了我们四个要素:|α ˻4˼| = 4

同样,对于任何n ∈ ℕ,我们有

 ˻n˼| = n

按要求。

  1. 许多理论要求的成员*仅是类型的代表,并且提供的操作是从类型术语*到其关联类型的显式映射。其他理论也允许文字类型本身是术语级实体。

“任意”功能?

现在,我们有一种设备可以将一种完全通用的幂级数表达为一种类型!

该系列

Σ[n  ℕ]f(n)X

成为类型

n : Nat f˼ n) × (Vec X n)

˻f˼ : Nat → Nat函数语言中合适的表示在哪里f。我们可以看到如下。

|∃n : Nat f˼ n) × (Vec X n)|
    = Σ[n : Nat]|α f˼ n) × (Vec X n)|          (property of  types)
    = Σ[n  ℕ]|α f˼ ˻n˼) × (Vec X ˻n˼)|        (switching Nat for ℕ)
    = Σ[n  ℕ]|α ˻f(n × (Vec X ˻n˼)|           (applying ˻f˼ to ˻n˼)
    = Σ[n  ℕ]|α ˻f(n)˼||Vec X ˻n˼|              (splitting product)
    = Σ[n  ℕ]f(n)|X|ⁿ                           (properties of α and Vec)

这到底有多“随意”?通过这种方法,我们不仅限于整数系数,而且还限于自然数。除此之外f,考虑到图灵完成,可以是任何东西具有依赖类型语言的情况下,我们可以用自然数系数表示任何解析函数。

我还没有研究这种情况与(例如)问题中提供的情况之间的相互作用,List X ≅ 1/(1 - X)或者在这种情况下这种否定和非整数“类型”可能具有什么意义。

希望这个答案对探索任意类型的函数可以走多远有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.