免责声明:当你考虑 ⊥ 时,很多东西并不能真正正常工作,所以为了简单起见,我将公然忽略它。
初步的几点:
请注意,“联合”可能不是这里 A+B 的最佳术语——具体来说a disjoint union http://en.wikipedia.org/wiki/Disjoint_union两种类型的区别,因为即使类型相同,两侧也是有区别的。无论如何,更常见的术语就是“总和类型”。
实际上,单例类型是所有单元类型。它们在代数运算下的行为相同,更重要的是,仍然保留了存在的信息量。
您可能还想要一个零类型。 Haskell 规定为Void
。不存在类型为零的值,就像存在一个类型为一的值一样。
这里还缺少一项重大操作,但我稍后会再讲。
正如您可能已经注意到的,Haskell 倾向于借用范畴论中的概念,并且上述所有内容都有一个非常简单的解释:
给定对象 A 和 BHask,他们的乘积 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。参数化自动保证了通用属性,我对名称的不那么微妙的选择应该能让你明白。这(&&&)
运算符定义在Control.Arrow
, 顺便一提。
上面的对偶是带有注射的副产品 A+Binl: A → A+B 且inr: B → A+B,其中给定任何类型 C 和函数f:A→C,g:B→C,可以定义配对f ||| g: A+B → C 使得明显的等价成立。同样,参数化自动保证了大多数棘手的部分。在这种情况下,标准注射很简单Left
and Right
配对是函数either
.
乘积和求和类型的许多属性都可以从上面导出。请注意,任何单例类型都是终端对象Hask任何空类型都是初始对象。
Returning to the aforementioned missing operation, in a cartesian closed category http://en.wikipedia.org/wiki/Cartesian_closed_category you have exponential objects http://en.wikipedia.org/wiki/Exponential_object that correspond to arrows of the category. Our arrows are functions, our objects are types with kind *
, and the type A -> B
indeed behaves as BA in the context of algebraic manipulation of types. If it's not obvious why this should hold, consider the type Bool -> A
. With only two possible inputs, a function of that type is isomorphic to two values of type A
, i.e. (A, A)
. For Maybe Bool -> A
we have three possible inputs, and so on. Also, observe that if we rephrase the copairing definition above to use algebraic notation, we get the identity CA × CB = CA+B.
As for why这一切都是有道理的——特别是为什么你使用幂级数展开式是合理的——请注意,上面的大部分内容都指的是某种类型的“居民”(即具有该类型的不同值),以证明代数行为。为了明确这一观点:
产品类型(A, B)
代表一个值,每个来自A
and B
,独立拍摄。所以对于任何固定值a :: A
, 有一个类型值(A, B)
对于每个居民B
。这当然是笛卡尔乘积,乘积类型的居民数是各因子的居民数的乘积。
总和类型Either A B
代表一个值A
or B
,区分左右分支。前面提到,这是一个不相交并集,sum类型的居民数是被加数的居民数之和。
指数型B -> A
表示类型值的映射B
到类型的值A
。对于任何固定参数b :: B
,任何值A
可以分配给它;类型的值B -> A
为每个输入选择一个这样的映射,这相当于多个副本的乘积A
as B
有居民,因此求幂。
虽然一开始很想将类型视为集合,但实际上在这种情况下效果并不好——我们有不相交的并集而不是标准的集合并集,对交集或许多其他集合运算没有明显的解释,而且我们通常不关心集合成员资格(将其留给类型检查器)。
另一方面,上面的结构花了很多时间谈论counting居民,以及列举类型的可能值在这里是一个有用的概念。这很快就会导致我们枚举组合数学 http://en.wikipedia.org/wiki/Enumerative_combinatorics,如果您查阅链接的维基百科文章,您会发现它所做的第一件事就是定义“对”和“并”,其含义与乘积和总和类型完全相同生成函数 http://en.wikipedia.org/wiki/Generating_function,然后使用与您所做的完全相同的技术对与 Haskell 列表相同的“序列”执行相同的操作。
Edit:哦,我认为这里有一个快速的奖励,它引人注目地证明了这一点。您在评论中提到对于树类型T = 1 + T^2
你可以得出身份T^6 = 1
,这显然是错误的。然而,T^7 = T
does成立,树和树的七元组之间的双射可以直接构造,参见。安德烈亚斯·布拉斯的《七树合一》 http://www.math.lsa.umich.edu/~ablass/comb.html.
Edit×2:关于其他答案中提到的“类型的导数”结构的主题,您可能还会喜欢这篇论文来自同一作者 https://personal.cis.strath.ac.uk/~conor/Dissect.pdf它进一步建立在这个想法的基础上,包括除法和其他有趣的东西的概念。