代数解释多态性

时间:2012-05-04 17:35:50

标签: haskell types

所以我理解类型的基本代数解释:

Either a b ~ a + b
(a, b) ~ a * b
a -> b ~ b^a
()   ~ 1
Void ~ 0 -- from Data.Void

...并且这些关系适用于具体类型,例如Bool,而不是像a这样的多态类型。我还知道如何通过根据以下同构转换Church编码,将具有多态类型的类型签名转换为具体类型表示:

(forall r . (a -> r) -> r) ~ a

所以,如果我有:

id :: forall a . a -> a

我知道这并不意味着id ~ a^a,但实际上意味着:

id :: forall a . (() -> a) -> a
id ~ ()
   ~ 1

类似地:

pair :: forall r . (a -> b -> r) -> r
pair ~ ((a, b) -> r) - > r
     ~ (a, b)
     ~ a * b

这让我想到了我的问题。这条规则的“代数”解释是什么:

(forall r . (a -> r) -> r) ~ a

对于每个具体类型的同构,我可以指向一个等价的代数规则,例如:

(a, (b, c)) ~ ((a, b), c)
a * (b * c) = (a * b) * c

a -> (b -> c) ~ (a, b) -> c
(c^b)^a = c^(b * a)

但我不理解代数平等,类似于:

(forall r . (a -> r) -> r) ~ a

4 个答案:

答案 0 :(得分:28)

这是身份仿函数的着名Yoneda lemma

查看this帖子以获取可读的介绍,并查看任何类别理论教科书以获取更多信息。

简单地说,在给定f :: forall r. (a -> r) -> r的情况下,您可以应用f id来获取a,相反,如果x :: a,您可以($x)获取forall r. (a -> r) -> r ($x) id == x }}。

这些操作是相反的。证明:

显然($(f id)) == f。我会说明

x :: a -> r

因为函数在所有参数上相等时都是相等的,所以我们取($(f id)) x == f x并显示

x (f id) == f x

f

由于f是多态的,它可以作为一种自然转换;这是 f_A Hom(A, A) → A (x.) ↓ ↓ x Hom(A, R) → R f_R 的自然图:

x . f == f . (x.)

所以(x . f) id == f x

插入身份{{1}}。 QED

答案 1 :(得分:7)

要(试图)回答实际问题(这比提出的更广泛问题的答案没那么有趣),问题因“类型错误”而形成错误

Either ~ (+) 
(,)    ~ (*)
(->) b ~ flip (^)
()   ~ 1
Void ~ 0 

这些映射类型都是整数,并在自然函数上键入构造函数。从某种意义上说,你有一个从类型类别到自然类别的仿函数。在另一个方向,你“忘记”的东西,因为类型保留代数结构,而自然抛弃它。即给定Either () ()你可以得到一个独特的自然,但鉴于这是自然的,你可以得到许多类型。

但这有所不同:

(forall r . (a -> r) -> r) ~ a

它将一种类型映射到另一种类型!它不是上述仿函数的一部分。它只是类型类别中的同构。所以让我们给出一个不同的符号<=>

现在我们有了

(forall r . (a -> r) -> r) <=> a

现在你注意到我们不仅可以向nats和箭头发送类型到箭头,还可以向其他同构发送一些同构:

(a, (b, c)) <=> ((a, b), c) ~ a * (b * c) = (a * b) * c

但这里发生了一些微妙的事情。从某种意义上说,后者在对上的同构是真的因为代数身份是真的。这就是说后者中的“同构”只是意味着这两种类型在我们的函子形象下是等价的。

我们需要直接证明前同构,这是我们开始得到基本问题的地方 - 给我们的nat函子,forall r.映射到什么?但答案是forall r.既不是类型,也不是类型之间有意义的箭头。

通过引入forall,我们已经从一阶类型转移。没有理由期望forall 应该适合我们上面的Functor,事实上,它没有。

所以我们可以像其他人一样探索为什么同构存在(这本身就非常有趣) - 但在这样做时我们已经放弃了问题的代数核心。我认为,一个可以回答的问题是,鉴于高阶类型和构造函数的类别是它们之间的箭头,有什么有意义的Functor?

修改: 所以现在我有另一种方法可以说明为什么添加多态会让事情变得疯狂。我们首先提出一个更简单的问题 - 给定的多态类型是否具有零或多于零的居民?这是type inhabitation problem,并且通过Curry-Howard来解决modified realizability中的一个问题,因为这与询问某个逻辑中的公式是否可以在适当的计算模型中实现是一回事。现在正如该页面所解释的那样,这在简单类型的lambda演算中是可判定的,但是PSPACE完全。但是,一旦我们转向更复杂的事情,例如通过添加多态性并转到系统F,那么它就变得不可判定了!

所以,如果我们无法决定一个任意类型是否有人居住,那么我们显然无法决定许多居民的生活方式!

答案 2 :(得分:4)

这是一个有趣的问题。我没有完整的答案,但这个评论太长了。

类型签名(forall r. (a -> r) -> r)可以表示为我说

  
    

对于你想要命名的任何类型r,如果你给我一个a并生成r的函数,那么我会给你一个{{1} }。

  

现在,这必须适用于任何类型r,但它可以是特定的类型r。所以我采用这个巧妙的技巧的方法是让a坐在某个地方,我会给函数(它为我产生a),然后我递给{{1}回到你身边。

但如果我坐着r,我可以把它给你:

  
    

如果你给我1,我会给你一个r

  

对应于类型签名a或简称a。通过这种非正式的论证我们有

  
    

1 -> a

  

下一步是生成相应的代数表达式,但我不清楚代数量如何与通用量化相互作用。我们可能需要等待专家!

答案 3 :(得分:4)

指向nLab的一些链接:


因此,在类别理论的设置中:

Type               | Modeled¹ as               | In category
-------------------+---------------------------+-------------
Unit               | Terminal object           | CCC
Bottom             | Initial object            |
Record             | Product                   |
Union              | Sum (coproduct)           |
Function           | Exponential               |
-------------------+---------------------------+-------------
Dependent product² | Right adjoint to pullback | LCCC
Dependent sum      | Left adjoint to pullback  |

¹)在适当的类别─CCC中,Haskell的总和非多态子集(link),Haskell的非总性状的CPO(link),{{3对于依赖类型的语言。

²)forall量化是依赖产品的特例:

∀(x :: *). y[x] ~ ∏(x : Set)y[x]

其中Set是所有小类型的LCCC