代数数据类型的“代数”表达式对于具有数学背景的人来说非常具有启发性。让我试着解释一下我的意思。
定义了基本类型
•
+
X
1
并使用X²
的简写X•X
和2X
等X+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²
现在,我作为数学家的第一直觉是坚持使用这些表达方式,并尝试解决L
和T
。我可以通过重复替换来做到这一点,但似乎更容易滥用符号,并假装我可以随意重新排列。例如,对于链接列表:
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个二叉树有三个元素等
所以我的问题是 - 我在这做什么?这些操作似乎没有道理(无论如何,代数数据类型的平方根究竟是什么?)但它们会产生明显的结果。两种代数数据类型的商是否在计算机科学中有任何意义,还是仅仅是符号诡计?
而且,或许更有趣的是,是否可以扩展这些想法?是否存在类型代数的理论,例如,允许类型上的任意函数,或类型是否需要幂级数表示?如果你可以定义一类函数,那么函数的组合是否有任何意义?
答案 0 :(得分:43)
二叉树在类型的半环中由等式T=1+XT^2
定义。通过构造,T=(1-sqrt(1-4X))/(2X)
由复数的半环中的相同方程定义。因此,考虑到我们在同一类代数结构中求解相同的方程式,我们看到一些相似之处实际上并不令人惊讶。
问题在于,当我们在复杂数字的半环中推理多项式时,我们通常使用复数形成环或甚至是场的事实,因此我们发现自己使用不适用于半环的减法等操作。但是,如果我们有一条允许我们从方程的两边取消的规则,我们通常可以从我们的论证中消除减法。这是Fiore and Leinster证明的有关环的许多论点可以转移到半环的事情。
这意味着您对戒指的大量数学知识可以可靠地转移到类型上。因此,一些涉及复数或幂级数的论证(在正式权力系列的环中)可以以完全严格的方式转移到类型。
然而,这个故事还有更多。通过显示两个幂级数相等来证明两种类型相等(比如说)是一回事。但您也可以通过检查电源系列中的术语来推断有关类型的信息。我不确定这里的正式声明应该是什么。 (我推荐paper上的Brent Yorgey combinatorial species进行一些密切相关的工作,但物种 与类型相同。)
我发现完全令人兴奋的是你发现的东西可以扩展到微积分。关于微积分的定理可以转移到类型的半环节。事实上,即使关于有限差分的论证也可以转移,你会发现数值分析中的经典定理在类型理论中有解释。
玩得开心!
答案 1 :(得分:20)
似乎您所做的只是扩展递归关系。
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 + ...
由于类型操作的规则就像算术运算规则一样,你可以使用代数方法来帮助你弄清楚如何扩展递归关系(因为它不是很明显)。
答案 2 :(得分:18)
我没有完整的答案,但这些操作倾向于“正常工作”。相关论文可能是Objects of Categories as Complex Numbers by Fiore and Leinster - 我在阅读sigfpe's blog on a related subject时遇到过那篇论文。该博客的其余部分是类似想法的金矿,值得一试!
顺便说一下,您还可以区分数据类型 - 这将为您提供适合数据类型的Zipper!
答案 3 :(得分: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使用公理在选择和顺序方面定义并行性;请参阅维基百科文章。我想知道包包的类比是什么
L = 1 /(1-X)
ACP样式编程对于文本解析器和GUI控制器很方便。
等规格searchCommand = clicked(searchButton)+ key(Enter)
cancelCommand = clicked(cancelButton)+ key(Escape)
通过使两个细化“clicked”和“key”隐式(比如Scala允许的函数),可以更简洁地写下。因此我们可以写:
searchCommand = searchButton + Enter
cancelCommand = cancelButton + Escape
右侧现在包含作为数据的操作数,而不是进程。在这个级别,没有必要知道隐式改进将把这些操作数转换为进程;他们不一定会改进投入行动;输出动作也适用,例如在测试机器人的规范中。
进程以数据的形式获取数据;因此我用“项目代数”这个词来表达。
答案 4 :(得分:6)
这是另一个小的补充 - 组合扩展中系数扩展的系数为什么应该工作的组合洞察,特别是关注可以使用微积分的Taylor-Maclaurin方法导出的系列。注意:您给出的操纵列表类型的示例系列扩展是Maclaurin系列。
由于其他答案和评论涉及代数类型表达式(总和,产品和指数)的行为,因此这个答案将忽略该细节并专注于类型' calculus'。
在这个答案中你可能会注意到引号中的引号很重要。有两个原因:
函数f : ℝ → ℝ
的{{3}}定义为
f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f⁽ⁿ⁾(0)Xⁿ + ...
其中f⁽ⁿ⁾
表示n
的{{1}}衍生物。
为了能够理解用类型解释的Maclaurin系列,我们需要理解我们如何在类型上下文中解释三件事:
f
0
事实证明,分析中的这些概念在类型世界中具有合适的对应物。
对于合适的对手来说,我的意思是什么?它应该具有同构的味道 - 如果我们能够在两个方向上保存真理,那么在一个上下文中可推导出的事实可以转移到另一个上下文。
那么类型表达式的导数是什么意思呢?事实证明,对于一个大型且表现良好(可区分的')类型的表达式和仿函数,有一种自然的操作,其行为类似于足以成为合适的解释!
为了破坏妙语,类似于差异化的操作就是制造单洞背景'。 Maclaurin series是进一步拓展这一特定点的绝佳场所,但单孔上下文((1/n!)
)的基本概念是它表示提取特定类型的单个子项的结果({ {1}})来自术语(类型da/dx
),保留所有其他信息,包括确定子项的原始位置所必需的信息。例如,表示列表的单孔上下文的一种方法是使用两个列表:一个用于在提取的项目之前的项目,一个用于之后的项目。
通过区分识别该操作的动机来自以下观察。我们写x
来表示类型为a
的类型为da/dx
的单孔上下文的类型。
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
代表通常的总和和产品类型。 +
和×
用于表示类型函数或类型表达式,f
表示在前面的表达式中用g
替换每个[f(x)/a]
的操作
这可以用无点样式编写,写f(x)
来表示函数a
的派生函数,因此:
f'
这可能是优选的。
如果我们使用类型和仿函数的同构类来定义导数,那么可以使等式变得严格和准确。
现在,我们特别注意到微积分中有关加法,乘法和合成的代数运算(通常称为求和,乘积和链条规则)的规则完全由'的运算反映出来。打个洞#39;此外,“打洞”的基本情况是在一个常量表达式中,或者术语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
是什么样的?好吧,我们当然无法访问f
类型的任何内容,因此任何需要f(0)
的{{1}}结构都不可用。剩下的是那些在他们缺席的情况下可以访问的术语,我们可以将其称为“不变”。或者'常数'部分类型。
对于一个明确的示例,请使用0
仿函数,该仿函数可以代数表示为f(x)
。当我们将此应用于x
时,我们得到Maybe
- 它就像x ↦ 1 + x
一样:唯一可能的值是0
值。对于列表,类似地,我们只获得与空列表对应的术语。
当我们将其归还并将类型1 + 0
解释为数字时,可以将其视为 count ,其中包含1
类型的任意数量(对于任何{可以在无法访问None
的情况下获取{1}}),即“空白”的数量为'术语
我担心我无法将f(0)
作为一种类型直接解释。
如果我们根据上述情况考虑类型f(x)
,我们会发现它可以被解释为x
的类型 - 类型为{{1}的术语的上下文} 尚未包含 x
- 也就是说,当我们整合'他们(1/n!)
次,生成的字词完全 f⁽ⁿ⁾(0)
n
s,不多也不少。然后将类型f(x)
解释为数字(如Maclaurin系列x
中的系数)只是计算有多少这样的空n
- 位置上下文。我们快到了!
但n
最终会在哪里?检查类型分化的过程'告诉我们,当多次应用时,它会保留“订单”。其中提取子项。例如,考虑x
类型的术语f⁽ⁿ⁾(0)
以及“挖洞”的操作'在它两次。我们得到两个序列
f
尽管两者都来自同一个词,因为有n
个方法从两个元素中取两个元素,保留顺序。一般来说,This方法可以从(1/n!)
中获取(x₀, x₁)
个元素。因此,为了计算具有x × x
元素的仿函数类型的配置数量,我们必须计算类型(x₀, x₁) ↝ (_₀, x₁) ↝ (_₀, _₁)
(x₀, x₁) ↝ (x₀, _₀) ↝ (_₁, _₀)
(where _ represents a 'hole')
并除以2! = 2
,完全< / em>与Maclaurin系列的系数一样。
因此除以n
结果只能解释为其本身。
首先,一些观察结果:
由于我们有链规则,如果我们将类型导数形式化为同构类,我们可以使用there are n!
。但隐式分化并不需要任何外来机动,如减法或除法!所以我们可以用它来分析递归类型定义。以列表为例,我们有
n
然后我们可以评估
n
获得Maclaurin系列中的f⁽ⁿ⁾(0)
系数。
但是,既然我们确信这些表达确实是严格的可区分的,如果只是隐含的,并且因为我们与函数ℝ→correspondence有对应关系,其中衍生物当然是唯一的,我们可以放心即使我们使用&#39;非法&#39;操作,结果有效。
现在,类似地,使用第二个观察,由于函数ℝ→correspondence的对应关系(它是同态吗?),我们知道,只要我们对函数具有 a感到满意Maclaurin系列,如果我们能够找到任何系列,可以应用上述原则使其严谨。
关于你关于函数组合的问题,我认为链规则提供了部分答案。
我不确定这适用于多少Haskell风格的ADT,但我怀疑它有很多,如果不是全部的话。我发现了这个事实的一个真正奇妙的证据,但是这个边际太小而不能包含它......
现在,当然,这只是解决这里发生的事情的一种方法,可能还有很多其他方法。
n!
,让我们得到一个空洞的&#39;该仿函数的术语。