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

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

定义了基本类型

  • 产品1 / (1 - X)
  • Union 1 / (1 - X)
  • Singleton 1 / (1 - X)
  • 单位1 / (1 - X)

并且使用L的220809397858153369和X+X等220809397858153369的简写,然后我们可以定义代数表达式,例如2208093978581533669。 链表

1 / (1 - X)L

和二叉树:

1 / (1 - X)L

现在,我作为数学家的第一直觉是坚持这些表达方式,并尝试解决1 / (1 - X)L.我可以通过重复替换来做到这一点,但似乎更容易滥用符号,并假装我可以重新排列它 将。 例如,对于链接列表:

1 / (1 - X)

1 / (1 - X)

1 / (1 - X)

我使用1 / (1 - X)的幂级数展开以完全不合理的方式得出一个有趣的结果,即L类型是T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...,或者它包含1个元素,或者它包含2个元素,或3等。

如果我们为二叉树做它会变得更有趣:

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

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

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

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

再次,使用幂级数扩展(使用Wolfram Alpha完成)。 这表达了非显而易见的(对我来说)事实,即只有一个二元树有1个元素,2个二叉树有两个元素(第二个元素可以在左边或右边的分支上),5个二叉树有三个元素等。

所以我的问题是 - 我在这做什么? 这些操作似乎没有道理(无论如何,代数数据类型的平方根究竟是什么?)但它们会产生明显的结果。 两种代数数据类型的商是否在计算机科学中有任何意义,还是仅仅是符号的诡计?

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

7个解决方案
129 votes

免责声明:当你考虑到⊥时,很多这种方法并没有真正起作用,所以为了简单起见,我会公然无视这一点。

一些初步要点:

  • 请注意,“联合”可能不是这里A + B的最佳术语 - 这特别是两种类型的不相交联合,因为即使它们的类型相同,双方也是有区别的。 对于它的价值,更常见的术语只是“和型”。

  • 单例类型实际上是所有单元类型。 它们在代数操作下表现相同,更重要的是,仍然保留了存在的信息量。

  • 您可能也想要零类型。 没有标准的名称,但最常见的名称是T = 1 + T^2.没有类型为零的值,就像有一个类型为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)= f并且同样对于g。 参数化自动保证了通用属性,而我不太精确的名称选择应该会给你一个想法。 顺便说一句,T = 1 + T^2运算符在T^6 = 1中定义。

  • 以上的对偶是产品A + B注入inl:A→A + B和inr:B→A + B,其中给出任何类型C和函数f:A→C,g:B→C,你可以 定义copairing f ||| g:A + B→C使得明显的等价性成立。 同样,参数化自动保证了大部分棘手的部分。 在这种情况下,标准注射仅为T = 1 + T^2T^6 = 1,并且copairing是功能T^7 = T

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

返回上述缺失操作,在笛卡尔闭合类别中,您具有与该类别的箭头对应的指数对象。 我们的箭头是函数,我们的对象是类型T = 1 + T^2的类型,类型T^6 = 1确实在类型的代数操作的上下文中表现为BA。 如果为什么这应该成立并不明显,请考虑类型T^7 = T。只有两个可能的输入,该类型的函数与两个b :: B类型的值同构,即A。对于B -> A,我们有三个可能的输入,依此类推。 另外,观察如果我们重新使用上面的共同定义来使用代数表示法,我们得到身份CA×CB = CA + B.

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

  • 产品类型T = 1 + T^2表示各自独立的T^6 = 1T^7 = T的值。 因此对于任何固定值b :: B,每个居民B -> A都有一个类型A的值。这当然是笛卡尔积,并且产品类型的居民数量是因素的居民数量的乘积。

  • 总和类型T = 1 + T^2表示来自T^6 = 1T^7 = T的值,左右分支区分。 如前所述,这是一个不相交的联盟,总和类型的居民数量是总数的居民数量的总和。

  • 指数类型T = 1 + T^2表示从类型T^6 = 1的值到类型T^7 = T的映射。对于任何固定参数b :: B,可以为其分配任何值A; 类型为B -> A的值为每个输入选择一个这样的映射,这相当于具有居民的AA的多个副本的乘积,因此取幂。

虽然最初将类型视为集合很诱人,但在这种情况下实际上并不能很好地工作 - 我们有不相交的联合而不是集合的标准联合,对交集或许多其他集合操作没有明显的解释,我们 通常不关心集合成员资格(将其留给类型检查器)。

另一方面,上面的结构花了很多时间讨论计算居民,并且枚举类型的可能值是一个有用的概念。 这很快就会引导我们进行枚举组合,如果你查阅链接的维基百科文章,你会发现它所做的第一件事就是定义“对”和“联合”,其含义与产品和总和类型完全相同。 生成函数,然后使用完全相同的技术对与Haskell列表相同的“序列”执行相同的操作。


编辑:噢,这是一个快速的奖金,我认为这一点非常引人注目。 您在评论中提到,对于树型T = 1 + T^2,您可以派生身份T^6 = 1,这显然是错误的。 然而,T^7 = T确实存在,并且可以直接构建树和七元组树之间的双射,参见 安德烈亚斯布拉斯的“七棵树合一”。

编辑×2:关于其他答案中提到的“类型衍生”结构的主题,你可能也会从同一作者中获得这篇论文,这篇论文进一步建立了这个理念,包括分裂的概念和其他有趣的东西。

C. A. McCann answered 2019-02-07T14:44:54Z
41 votes

二进制树由类型的半环中的等式T=1+XT^2定义。 通过构造,T=(1-sqrt(1-4X))/(2X)在复数的半环中由相同的等式定义。 因此,考虑到我们在同一类代数结构中求解相同的方程式,我们看到一些相似之处实际上并不令人惊讶。

问题在于,当我们在复杂数字的半环中推理多项式时,我们通常使用复数形成环或甚至是场的事实,因此我们发现自己使用不适用于半环的减法等操作。 但是,如果我们有一条允许我们从方程的两边取消的规则,我们通常可以从我们的论证中消除减法。 这是菲奥雷和伦斯特证明的那种事情,表明关于戒指的许多争论可以转移到半环。

这意味着您对戒指的大量数学知识可以可靠地转移到类型上。 因此,一些涉及复数或幂级数的论证(在正式权力系列的环中)可以以完全严格的方式延续到类型。

然而,这个故事还有更多。 通过显示两个幂级数相等来证明两种类型相等(比如说)是一回事。 但您也可以通过检查电源系列中的术语来推断有关类型的信息。 我不确定这里的正式声明应该是什么。 (我推荐Brent Yorgey撰写的关于组合物种的论文,其中一些作品密切相关,但物种与类型不同。)

我发现完全令人兴奋的是你发现的东西可以扩展到微积分。 关于微积分的定理可以转移到类型的半环节。 实际上,甚至关于有限差分的论证也可以转移,你会发现数值分析中的经典定理在类型理论中有解释。

玩得开心!

sigfpe answered 2019-02-07T14:46:01Z
20 votes

似乎你所做的只是扩展递归关系。

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 + ...

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

newacct answered 2019-02-07T14:46:39Z
18 votes

我没有完整的答案,但这些操作倾向于“正常工作”。 一篇相关的论文可能是菲奥雷和伦斯特的“类别为对象的类别” - 我在阅读sigfpe关于相关主题的博客时遇到过那篇文章; 该博客的其余部分是类似想法的金矿,值得一试!

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

yatima2975 answered 2019-02-07T14:47:18Z
10 votes

通信过程代数(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使用公理在选择和顺序方面定义并行性; 请参阅维基百科文章。 我想知道包包的类比是什么

L = 1 /(1-X)

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

searchCommand = clicked(searchButton)+ key(回车)

取消Command =单击(取消按钮)+键(Escape)

可以通过使两个细化“clicked”和“key”隐式(比如Scala允许的函数)更简洁地写下来。 因此我们可以写:

searchCommand = searchButton + Enter

cancelCommand = cancelButton + Escape

右侧现在包含作为数据的操作数,而不是进程。 在这个级别,没有必要知道隐式改进将把这些操作数转换为进程; 他们不一定会改进投入行动; 输出动作也适用,例如 在测试机器人的规范中。

进程以数据的形式获得数据; 因此,我称之为“项代数”。

André van Delft answered 2019-02-07T14:50:01Z
6 votes

微积分和Maclaurin系列与类型

这是另一个小的补充 - 组合扩展系列扩展中的系数应该“起作用”的组合洞察,特别是关注可以使用来自微积分的泰勒 - 麦克劳林方法得出的系列。 注意:您给出的操纵列表类型的示例系列扩展是Maclaurin系列。

由于其他答案和评论涉及代数类型表达式(总和,产品和指数)的行为,因此这个答案将忽略该细节并关注类型“微积分”。

在这个答案中你可能会注意到引号中的引号很重要。 有两个原因:

  • 我们的业务是从一个域向另一个域的实体提供解释,并且以这种方式划分这样的外国概念似乎是恰当的。
  • 一些概念将能够更严格地形式化,但形状和想法似乎比细节更重要(并且占用更少的空间)。

Maclaurin系列的定义

Maclaurin系列函数0定义为

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

其中0的意思是n的衍生物n

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

  • 一个(可能是多个)衍生物
  • 将函数应用于0
  • 0这样的术语

事实证明,这些来自分析的概念在类型世界中具有合适的对应物。

“合适的对手”是什么意思? 它应该具有同构的味道 - 如果我们能够在两个方向上保存真理,那么在一个上下文中可导出的事实可以转移到另一个上下文中。

微积分与类型

那么类型表达式的导数是什么意思呢? 事实证明,对于一个大型且表现良好(“可区分”)类型的表达式和仿函数,有一种自然操作,其行为类似于足以成为合适的解释!

为了破坏妙语,类似于差异化的操作就是制造“单洞背景”。 这是进一步拓展这一特定点的绝佳场所,但单孔上下文的基本概念(0)是它表示从一个术语(类型n)中提取特定类型的单个子项(n)的结果 ),保留所有其他信息,包括确定子项目原始位置所必需的信息。 例如,表示列表的单孔上下文的一种方法是使用两个列表:一个用于在提取的一个之前的项目,一个用于之后的项目。

通过区分识别该操作的动机来自以下观察。 我们写0表示类型为n的单孔上下文的类型,类型为n

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

这里,0n分别代表具有恰好一个且正好为零的居民的类型,并且nn表示通常的总和和产品类型。 f⁽ⁿ⁾(0)n!用于表示类型函数或类型表达式,x表示在前面的表达式中替换f⁽ⁿ⁾(0)的每个f的操作。

这可以用无点样式编写,编写0表示类型函数n的派生函数,因此:

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

这可能是优选的。

如果我们使用类型和仿函数的同构类来定义导数,那么可以使等式变得严格和精确。

现在,我们特别注意到微积分中有关加法,乘法和组合的代数运算的规则(通常称为求和,产品和链条规则)完全由“打洞”的操作反映出来。 此外,在一个恒定表达式中“制作孔”的基本情况或者术语0本身也表现为区分,因此通过归纳,我们得到所有代数类型表达式的类似区分的行为。

现在我们可以解释分化,0th'衍生'的类型表达式是什么意思?n是什么意思? 它是一种代表n-place contexts的类型:当用n类型f⁽ⁿ⁾(0)'填充'时的条款产生n!.还有另一个与'x'相关的关键观察结果。

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

我们已经在类型世界中对0进行了解释:没有成员的空类型。 从组合的角度来看,将类型函数应用于它是什么意思? 更具体地说,假设n是一个类型函数,n是什么样的? 好吧,我们肯定无法访问n类型的任何内容,因此任何需要n!f⁽ⁿ⁾(0)的结构都不可用。 剩下的是那些在他们缺席时可以访问的术语,我们可以将其称为该类型的“不变”或“常量”部分。

有关一个明确的示例,请使用0仿函数,代数可以代表n.当我们将其应用于n时,我们得到n - 它就像f⁽ⁿ⁾(0):唯一可能的值是n!值。 对于列表,类似地,我们只获得与空列表对应的术语。

当我们把它带回来并将类型0解释为数字时,它可以被认为是可以获得n(对于任何n)类型的数量的计数而无需访问n:即,空的数量 像'条款。

把它放在一起:完整解释Maclaurin系列

我恐怕我想不出一个适当的直接解释0作为一种类型。

但是,如果我们根据上述情况考虑类型0,我们看到它可以被解释为类型为n的地方类型n的类型,它们还没有包含n - 也就是说,当我们' 整合'他们f⁽ⁿ⁾(0)次,由此产生的期限恰好是n! xs,不多也不少。 然后对类型f⁽ⁿ⁾(0)的解释作为数字(如在Maclaurin系列f的系数中)仅仅是对有多少这样的空n位置上下文的计数。 我们快到了!

0最终在哪里? 检查“区分”类型的过程向我们表明,当多次应用时,它会保留提取子项的“顺序”。 例如,考虑n类型的术语n以及两次“制作孔”的操作。 我们得到两个序列

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

即使两者都来自同一个词,因为有0方法从两个中取两个元素,保留顺序。 一般来说,有0方法从n获取n元素。因此,为了获得具有n元素的仿函数类型的配置数,我们必须计算类型f⁽ⁿ⁾(0)并除以n!,完全如 Maclaurin系列的系数。

因此除以0就可以简单地解释为自身。

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

首先,一些观察:

  • 如果函数f:ℝ→ℝ具有导数,则该导数是唯一的
  • 类似地,如果函数f:ℝ→ℝ是解析的,它只有一个相应的多项式系列

由于我们有链规则,如果我们将类型导数形式化为同构类,我们可以使用隐式区分。 但隐式分化不需要任何外来机动,如减法或除法! 所以我们可以用它来分析递归类型定义。 以列表为例,我们有

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

然后我们可以评估

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

在Maclaurin系列中获得系数0

但由于我们确信这些表达式确实是严格“可区分的”,如果只是隐含的,并且由于我们与函数ℝ→ℝ具有对应关系,其中衍生物当然是唯一的,我们可以放心,即使我们使用'获得值' 非法'操作,结果有效。

现在,类似地,使用第二个观察,由于函数ℝ→correspondence的对应关系(它是同态吗?),我们知道,如果我们对函数有Maclaurin系列感到满意,如果我们能找到任何系列 总而言之,上述原则可以应用于使其严谨。

至于你关于函数组合的问题,我认为链规则提供了部分答案。

我不确定这适用于多少Haskell风格的ADT,但我怀疑它有很多,如果不是全部的话。 我发现了这个事实的一个真正奇妙的证据,但这个边际太小而无法包含它......

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

摘要:TL; DR

  • 类型'分化'对应于'打洞'。
  • 将一个仿函数应用于0可以获得该仿函数的“空洞”术语。
  • 因此,Maclaurin幂系列(有些)严格对应于枚举具有一定数量元素的仿函数类型的成员数。
  • 隐性分化使这更加不透水。
  • 衍生物的独特性和动力系列的独特性意味着我们可以捏造细节并且它起作用。
Oly answered 2019-02-07T14:57:16Z
3 votes

依赖型理论和“任意”型函数

我对这个问题的第一个答案是高概念和低细节,并反映在子问题上,'发生了什么?'; 这个答案将是相同的,但专注于子问题,“我们可以得到任意类型的函数吗?”。

sum和product的代数运算的一个扩展是所谓的“大运算符”,它代表序列的总和和乘积(或者更一般地说,是域上函数的和与乘积),通常分别写成List X ≅ 1/(1 - X)f。。 请参阅Sigma Notation。

所以总和

a₀ + a₁X + a₂X² + ...

可能是写的

Σ[i ∈ ℕ]aᵢXⁱ

例如,List X ≅ 1/(1 - X)是一些实数序列。 该产品将用f代替Successor进行类似的表示。

当你从远处看时,这种表达看起来很像List X ≅ 1/(1 - X)中的“任意”函数; 我们当然限于可表达系列及其相关的分析功能。 这是类型理论中的表示的候选者吗?非也!

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

库里 - 霍华德和依赖类型

“库里 - 霍华德同构”开始的生活是一种观察,即简单类型的lambda演算的术语和类型判断规则完全对应于应用于直觉主义命题逻辑的自然演绎(由Gentzen制定),类型代替命题 尽管两者是独立发明/发现的,但代替证据的术语。 从那以后,它一直是类型理论家的巨大灵感来源。 要考虑的最明显的事情之一是命题逻辑的这种对应是否以及如何可以扩展到谓词或更高阶逻辑。 依赖型理论最初源于这种探索途径。

关于简单类型lambda演算的Curry-Howard同构的介绍,请参见此处。 例如,如果我们要证明List X ≅ 1/(1 - X),我们必须证明f并证明Successor; 合并证明只是一对证明:每个合一一个。

在自然演绎中:

Γ ⊢ A    Γ ⊢ B
Γ ⊢ A ∧ B

在简单类型的lambda演算中:

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

对于List X ≅ 1/(1 - X)和求和类型,f和函数类型以及各种消除规则,存在类似的对应关系。

无法证实的(直觉上错误的)命题对应于无人居住的类型。

通过类型作为逻辑命题的类比,我们可以开始考虑如何在类型世界中建模谓词。 有许多方法已经形式化(参见Martin-Löf的直觉类型理论对于广泛使用的标准的介绍),但抽象方法通常观察到谓词就像一个带有自由项变量的命题,或者, 一个以命题为术语的函数。 如果我们允许类型表达式包含术语,那么lambda演算风格的处理立即呈现为一种可能性!

仅考虑建设性证据,List X ≅ 1/(1 - X)的证明是什么? 我们可以将其视为证明函数,将术语(f)作为其相应命题的证明(Successor)。 所以类型(命题)|Successor a| = 1 + |a|的成员(证明)是'依赖函数',其对于˻n˼中的每个a给出一个类型为Nat的术语。

List X ≅ 1/(1 - X)怎么样? 我们需要f,Successor的任何成员以及|Successor a| = 1 + |a|的证明。因此类型(命题)a的成员(证明)是'依赖对':Nat中的杰出术语Nat,以及n型的术语。

符号:我会用的

∀x ∈ X...

关于List X ≅ 1/(1 - X)类成员的实际陈述,以及

∀x : X...

对于类型List X ≅ 1/(1 - X),对应于通用量化的类型表达式。同样适用于f

组合注意事项:产品和总和

除了与命题类型的Curry-Howard对应,我们还有代数类型与数字和函数的组合对应关系,这是这个问题的主要观点。 令人高兴的是,这可以扩展到上面列出的依赖类型!

我将使用模数表示法

|A|

表示类型List X ≅ 1/(1 - X)的“大小”,以明确问题中列出的对应关系,类型和数字之间的对应关系。 请注意,这是理论之外的概念; 我并不声称语言中需要任何此类运算符。

让我们计算一下类型的可能(完全简化的,规范的)成员

∀x : X.P(x)

这是List X ≅ 1/(1 - X)类型的术语List X ≅ 1/(1 - X)的类型依赖函数类型Successor.每个这样的函数必须具有|Successor a| = 1 + |a|的每个项的输出,并且此输出必须是特定类型。 对于˻n˼中的每个a,然后,这给出Nat'选择'的输出。

妙语是

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

如果List X ≅ 1/(1 - X)f,这当然没有多大意义,但适用于代数类型。

同样,一个类型的术语

∃x : X.P(x)

List X ≅ 1/(1 - X)f的类型,因此给定Successor|Successor a| = 1 + |a|可以与a的任何成员构成一个合适的对,给出˻n˼'选项'。

因此,

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

同样的警告。

这证明了使用符号List X ≅ 1/(1 - X)f的理论中依赖类型的通用符号,并且实际上许多理论模糊了“为所有”和“产品”以及“有”和“和”之间的区别,这是由于上述对应。

我们越来越近了!

向量:表示依赖元组

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

Σ[n ∈ ℕ]Xⁿ

作为类型表达式?

不完全的。 虽然我们可以非正式地考虑Haskell中List X ≅ 1/(1 - X)这样的表达式的含义,其中f是一个类型而Successor是一个自然数,但这是滥用符号; 这是一个包含数字的类型表达式:明显不是有效的表达式。

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

在这个答案的持续时间,让

Vec X n

f类型值的长度类型List X ≅ 1/(1 - X)

技术上,List X ≅ 1/(1 - X)这里是自然数的系统中的表示,而不是实际的自然数。 我们可以将Peano风格的自然数(f)表示为零(Successor)或另一个自然数的后继(|Successor a| = 1 + |a|),并且a我写˻n˼表示Nat中的术语,表示n.例如,˻3˼S (S (S 0))

然后我们有

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

任何List X ≅ 1/(1 - X)

Nat类型:促进ℕ术语到类型

现在我们可以编码表达式

Σ[n ∈ ℕ]Xⁿ

作为类型。 这个特定的表达式将产生一种类型,该类型当然与该问题中所确定的List X ≅ 1/(1 - X)的列表类型同构。 (不仅如此,但从类别理论的角度来看,类型函数 - 它是一个函子 - 将f带到上面的类型中,与List函数自然是同构的。)

“任意”功能的最后一个难题是如何进行编码

f : ℕ → ℕ

表达式如

Σ[n ∈ ℕ]f(n)Xⁿ

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

我们已经理解了代数类型与数字的对应关系,允许我们从类型映射到数字,并将类型函数映射到数字函数。 我们也可以走另一条路! - 采用自然数,显然有一个可定义的代数类型,有许多术语成员,无论我们是否有依赖类型。 我们可以通过归纳在类型理论之外轻松证明这一点。 我们需要的是一种从系统内部的自然数字到类型进行映射的方法。

一个令人愉快的认识是,一旦我们有依赖类型,通过归纳证明和通过递归构造变得非常相似 - 实际上它们在许多理论中是完全相同的。 既然我们可以通过归纳证明存在满足我们需求的类型,那么我们是否应该无法构建它们?

有几种方法可以在术语级别表示类型。 我将在这里使用一个假想的Haskellish符号List X ≅ 1/(1 - X)用于类型的宇宙,它本身通常被认为是依赖设置中的类型。

同样,至少还有尽可能多的方法来表示'List X ≅ 1/(1 - X)-elimination',因为存在依赖型理论。 我将使用Haskellish模式匹配表示法。

我们需要一个映射,List X ≅ 1/(1 - X)fSuccessor,与属性

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

以下伪定义就足够了。

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

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

所以我们看到List X ≅ 1/(1 - X)的行为反映了继承者f的行为,使其成为一种同态。 Successor是一种类型函数,它为一个类型的成员数量“加1”; 对于任何具有已定义大小的a,即|Successor a| = 1 + |a|

例如List X ≅ 1/(1 - X)(即f),是

Successor (Successor (Successor (Successor Zero)))

而且这种类型的条款是

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

给我们正好四个要素:List X ≅ 1/(1 - X)

同样,对于任何List X ≅ 1/(1 - X),我们都有

|α ˻n˼| = n

按要求。

  1. 许多理论要求List X ≅ 1/(1 - X)的成员仅仅是类型的代表,并且提供操作作为从类型f的术语到其相关类型的显式映射。 其他理论允许文字类型本身成为术语级实体。

'任意'功能?

现在我们有一个装置来表达一个完全通用的电源系列作为一种类型!

该系列

Σ[n ∈ ℕ]f(n)Xⁿ

成为类型

∃n : Nat.α (˻f˼ n) × (Vec X n)

其中List X ≅ 1/(1 - X)是函数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)

这是多么“武断”? 我们不仅限于这种方法的整数系数,而且还限于自然数。 除此之外,List X ≅ 1/(1 - X)可以是任何东西,给定具有依赖类型的图灵完备语言,我们可以用自然数系数表示任何分析函数。

我没有调查过这种情况的相互作用,例如List X ≅ 1/(1 - X)问题中提供的情况,或者在这种情况下可能有什么样的消极和非整数'类型'。

希望这个答案能够探索我们可以使用任意类型函数走多远。

Oly answered 2019-02-07T15:06:48Z
translate from https://stackoverflow.com:/questions/9190352/abusing-the-algebra-of-algebraic-data-types-why-does-this-work