原文:https://dev.to/gcanti/getting-started-with-fp-ts-category-4c9a
这篇文章的标题是《Getting started with fp-ts: Category》,由Giulio Canti在2019年3月20日发表,并在2019年7月1日更新。文章是关于在fp-ts
库中使用Category
类型类的入门指南。
在之前的文章中,我们看到了函数式编程中使用的一些基本抽象:Eq
、Ord
、Semigroup
和Monoid
。
在接下来的文章中,我们将探索一些使函数式编程更有趣的高级抽象。
从历史上看,fp-ts
中第一个高级抽象是Functor
,但在我们谈论函子之前,我们需要了解一些关于类别的知识,因为函子是建立在它们之上的。
函数式编程的一个基石是组合。但那到底意味着什么?我们什么时候可以说两个东西_组合_?我们什么时候可以说东西组合得_好_?
我们需要一个形式化的组合定义。这就是类别所涉及的。
类别捕捉了组合的本质。
类别
类别的定义有点长,所以我将把它的定义分成两部分:
第一部分是技术性的(首先我们需要定义它的组成部分)
第二部分将包含我们最感兴趣的内容:组合的概念
第一部分(定义)
一个类别是一对(Objects, Morphisms)
,其中:
Objects
是一系列对象Morphisms
是一系列对象之间的态射(或箭头)
注意。这里的“对象”一词与OOP无关,你可以将对象视为无法检查的黑盒子,或者甚至是态射的一些辅助占位符。
每个态射f
都有一个源对象A
和一个目标对象B
,其中A
和B
都在Objects
中。
我们写作f: A ⟼ B
,并说“f是从A到B的态射”。
第二部分(组合)
有一个操作∘
,名为“组合”,必须满足以下属性
(态射的组合)每当
f: A ⟼ B
和g: B ⟼ C
是Morphisms
中的两个态射时,必须存在第三个态射g ∘ f: A ⟼ C
在Morphisms
中,这是f
和g
的_组合_(结合性)如果
f: A ⟼ B
、g: B ⟼ C
和h: C ⟼ D
,那么h ∘ (g ∘ f) = (h ∘ g) ∘ f
(恒等式)对于每个对象
X
,存在一个态射identity: X ⟼ X
称为X
的_恒等态射_,使得对于每个态射f: A ⟼ X
和每个态射g: X ⟼ B
,我们有identity ∘ f = f
和g ∘ identity = g
。
示例
(来源:[wikipedia.org上的类别](https://en.wikipedia.org/wiki/Category_(mathematics))
这个类别非常简单,只有三个对象和六个态射(1A、1B、1C分别是A
、B
、C
的恒等态射)。
类别作为编程语言
一个类别可以被解释为一个类型化编程语言的简化模型,其中:
对象是类型
态射是函数
∘
是通常的函数组合
这个图
可以被解释为一个相当简单的、想象的编程语言,只有三种类型和一小撮函数。
例如:
A = string
B = number
C = boolean
f = string => number
g = number => boolean
g ∘ f = string => boolean
实现可能是这样的:
function f(s: string): number {
return s.length
}
function g(n: number): boolean {
return n > 2
}
// h = g ∘ f
function h(s: string): boolean {
return g(f(s))
}
TypeScript的类别
我们可以定义一个名为_TS_的类别,作为TypeScript语言的模型,其中:
对象是所有TypeScript类型:
string
、number
、Array<string>
,...态射是所有TypeScript函数:
(a: A) => B
、(b: B) => C
,...其中A
、B
、C
,...是TypeScript类型恒等态射全部编码为一个多态函数
const identity = <A>(a: A): A => a
态射的组合是通常的函数组合(它是结合的)
作为TypeScript的模型,_TS_可能看起来太有限了:没有循环,没有if
,几乎没有什么...尽管如此,这个简化的模型对于我们的主要目的来说已经足够丰富了:对一个定义良好的组合概念进行推理。
组合的核心问题
在_TS_中,我们可以组合两个通用函数f: (a: A) => B
和g: (c: C) => D
,只要B = C
。
function compose<A, B, C>(g: (b: B) => C, f: (a: A) => B): (a: A) => C {
return a => g(f(a))
}
但如果B != C
呢?我们如何_组合_这样的函数?我们是不是应该就此放弃?
在下一篇文章中,我们将看到在什么条件下这样的组合是可能的。我们将谈论函子。
TLDR:函数式编程就是关于组合。