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

Haskell 滥用代数数据类型的代数-为什么这样做?,haskell,functional-programming,algebraic-data-types,miranda,Haskell,Functional Programming,Algebraic Data Types,Miranda,代数数据类型的“代数”表达式对于有数学背景的人来说很有启发性。让我试着解释一下我的意思 定义了基本类型 产品• 联合+ 单件X 单元1 对于X•X和X+X等,使用速记X²,我们可以定义代数表达式,例如链表 数据列表a=Nil|Cons a(列表a)↔ L=1+X•L 和二叉树: 数据树a=Nil |分支a(树a)(树a)↔ T=1+X•T² 现在,作为一名数学家,我的第一本能是疯狂地使用这些表达式,并尝试求解L和T。我可以通过重复替换来实现这一点,但似乎更容易可怕地滥用符号并假装我可以随意重

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

定义了基本类型

  • 产品
  • 联合
    +
  • 单件
    X
  • 单元
    1
对于
X•X
X+X
等,使用速记
,我们可以定义代数表达式,例如链表

数据列表a=Nil|Cons a(列表a)
↔ <代码>L=1+X•L

和二叉树:

数据树a=Nil |分支a(树a)(树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⁴ + ...

再次使用幂级数展开(使用完成)。这表达了一个不明显的事实(对我来说),即只有一个二叉树有一个元素,两个二叉树有两个元素(第二个元素可以在左边或右边的分支上),五个二叉树有三个元素等等

所以我的问题是-我在这里做什么?这些操作似乎不合理(代数数据类型的平方根到底是多少?),但它们会导致合理的结果。两种代数数据类型的商在计算机科学中有什么意义吗,还是仅仅是符号上的诡计


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

我没有一个完整的答案,但这些操作往往“有效”。一篇相关的论文可能是——我在阅读时偶然发现的;该博客的其余部分是类似想法的金矿,值得一看


顺便说一句,您还可以区分数据类型-这将为数据类型找到合适的拉链

看来你所做的只是扩展递归关系

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

由于类型上的运算规则与算术运算规则类似,因此您可以使用代数方法来帮助您了解如何扩展递归关系(因为它并不明显)。

免责声明:当您考虑⊥, 因此,为了简单起见,我将公然忽略这一点

一些初始点:

  • 注意,“联合”在这里可能不是A+B的最佳术语——这是两种类型中的一种,因为即使它们的类型相同,双方也会有所区别。就其价值而言,更常见的术语是简单的“总和类型”

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

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

这里还有一个主要的行动没有完成,但我马上就回来

正如您可能已经注意到的,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。参数性自动保证了通用属性,我对名称的选择不那么微妙,这应该会让你明白这一点。顺便说一下,
    (&&&)
    运算符在
    控件中定义。箭头

  • 上面的对偶是带有注射inl:A的副产物A+B→ A+B和印度卢比:B→ A+B,其中给定任何类型C和函数f:A→ C、 g:B→ C、 您可以定义共配对f | | | g:A+B→ C使明显的等价物成立。同样,参数化可以自动保证大部分棘手的部分。在这种情况下,标准注入仅为
    ,同时是功能

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

返回到前面提到的缺失操作,在中,您有对应于类别箭头的。我们的箭头是函数,我们的对象是种类
*
的类型,类型
A->B
在代数操作的上下文中确实表现为BA
a₀ + a₁X + a₂X² + ...
Σ[i ∈ ℕ]aᵢXⁱ
Γ ⊢ A    Γ ⊢ B
Γ ⊢ A ∧ B
Γ ⊢ a : A    Γ ⊢ b : B
Γ ⊢ (a, b) : A × B
∀x ∈ X...
∀x : X...
|A|
∀x : X.P(x)
|∀x : X.P(x)| = Π[x : X]|P(x)|
∃x : X.P(x)
|∃x : X.P(x)| = Σ[x : X]|P(x)|
Σ[n ∈ ℕ]Xⁿ
Vec X n
|Vec X ˻n˼| = |X|ⁿ
Σ[n ∈ ℕ]Xⁿ
f : ℕ → ℕ
Σ[n ∈ ℕ]f(n)Xⁿ
∀n ∈ ℕ.|α ˻n˼| = n.
data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe

α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)
Successor (Successor (Successor (Successor Zero)))
Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))
|α ˻n˼| = n
Σ[n ∈ ℕ]f(n)Xⁿ
∃n : Nat.α (˻f˼ n) × (Vec X n)
|∃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)