范畴(Category)的三个要素:对象、态射、恒等
——编程中的“类型”是对象,“函数”是态射,“id”是恒等函数
“范畴论不是数学家的玩具,
它是程序员的抽象操作系统。”
一、什么是范畴(Category)?
在数学中,一个范畴(Category) 由三部分构成:
- 对象(Objects)
- 态射(Morphisms)
- 恒等态射(Identity)与复合律(Composition)
这听起来抽象,但其实在编程中无处不在。
二、编程即范畴:从理论到代码
1. 对象(Objects) = 类型(Types)
在编程中,每个类型都是一个“对象”:
string
number
boolean
User
Array<string>
Promise<number>这些不是值,而是值的集合,即范畴中的“对象”。
string 是一个对象number → boolean(函数类型)也是一个对象
2. 态射(Morphisms) = 函数(Functions)
态射是对象之间的映射。
在编程中,这就是函数。
const strToLen = (s: string): number => s.length;
// string → number这个函数就是从对象 string 到对象 number 的态射。
其他例子:
const double = (n: number): number => n * 2; // number → number
const validate = (s: string): boolean => s !== ''; // string → boolean3. 恒等态射(Identity) = 恒等函数
每个对象必须有一个“不做任何事”的态射,称为恒等态射(Identity Morphism)。
在编程中,这就是恒等函数(id):
const id = <A>(x: A): A => x;它满足:
id(string) → string
id(number) → number
id(User) → User无论输入什么,原样返回。
三、范畴的两大定律
要称为“范畴”,必须满足两个基本定律:
1. 恒等律(Identity Law)
对任意态射 f: A → B,有:f ∘ id_A = f = id_B ∘ f
在代码中:
const f = (x: string): number => x.length;
// 左恒等:id 后 f
f(id('hello')) === f('hello') // true
// 右恒等:f 后 id
id(f('hello')) === f('hello') // true恒等函数不改变结果。
2. 结合律(Associativity)
对态射 f: A → B, g: B → C, h: C → D,有:h ∘ (g ∘ f) = (h ∘ g) ∘ f
在代码中就是函数组合的结合性:
const f = (x: number) => x + 1;
const g = (x: number) => x * 2;
const h = (x: number) => x.toString();
// 先 g∘f,再 h
h(g(f(5)))
// vs
// 先 h∘g,再 f
(h.compose(g))(f(5))
// 结果相同只要顺序不变,分组方式不影响结果。
四、编程语言就是一个范畴!
我们称它为 Hask(Haskell 风格)或 TypeCat:
| 数学概念 | 编程对应 |
|---|---|
| 对象(Objects) | 类型(string, number, User) |
| 态射(Morphisms) | 函数(f: A → B) |
| 恒等态射 | id(x) = x |
| 态射复合 | compose(f, g) 或 g(f(x)) |
只要你遵守类型系统和纯函数原则,你的程序就在一个合法的范畴中运行。
五、为什么这对程序员重要?
1. 理解高阶抽象的基础
- 函子(Functor):范畴之间的映射
- 自然变换(Natural Transformation):函子之间的映射
- Monad:自函子范畴上的幺半群(开玩笑?不,是真的)
没有范畴论,这些概念就像黑魔法。
2. 指导 API 设计
如果你设计一个库,让它“符合范畴”:
- 提供
id - 支持
compose - 确保结合律成立
用户就能安全地组合你的函数。
3. 类型系统的深层意义
类型不只是防止错误,
它们是范畴中的对象,
函数是态射,
整个程序是态射的复合网络。
六、代码示例:构建一个简单的范畴
// 对象:类型(用字符串表示)
type Type = 'String' | 'Number' | 'Boolean';
// 态射:函数及其类型签名
interface Morphism {
from: Type;
to: Type;
fn: Function;
}
// 恒等态射
const identity = <T>(x: T): T => x;
const idString: Morphism = {
from: 'String',
to: 'String',
fn: identity
};
// 复合函数
const compose = (g: Morphism, f: Morphism): Morphism => ({
from: f.from,
to: g.to,
fn: (x: any) => g.fn(f.fn(x))
});
// 使用
const strToLen: Morphism = {
from: 'String',
to: 'Number',
fn: (s: string) => s.length
};
const lenToBool: Morphism = {
from: 'Number',
to: 'Boolean',
fn: (n: number) => n > 5
};
const strToBool = compose(lenToBool, strToLen);
strToBool.fn('Hello'); // false
strToBool.fn('HelloWorld'); // true你刚刚实现了一个微型范畴!
七、工程启示
在库设计中:
- 提供
id和compose工具 - 确保函数纯正
- 文档化类型转换
在团队协作中:
- 用“类型即对象,函数即变换”思维沟通
- 把业务逻辑看作“态射的组合”
结语:范畴论是“抽象的语法”
它不关心你做什么业务,只关心你如何组合。
当你写下:
pipe(
validate,
transform,
save
)你不是在写代码,
你是在构造一个从 Input 到 Saved 的态射。
而 validate、transform、save 都是从一个类型到另一个类型的箭头。
编程,本质上是范畴中的路径绘制。