Skip to content

范畴(Category)的三个要素:对象、态射、恒等

——编程中的“类型”是对象,“函数”是态射,“id”是恒等函数

“范畴论不是数学家的玩具,
它是程序员的抽象操作系统。”

一、什么是范畴(Category)?

在数学中,一个范畴(Category) 由三部分构成:

  1. 对象(Objects)
  2. 态射(Morphisms)
  3. 恒等态射(Identity)与复合律(Composition)

这听起来抽象,但其实在编程中无处不在。

二、编程即范畴:从理论到代码

1. 对象(Objects) = 类型(Types)

在编程中,每个类型都是一个“对象”

ts
string
number
boolean
User
Array<string>
Promise<number>

这些不是值,而是值的集合,即范畴中的“对象”。

string 是一个对象
number → boolean(函数类型)也是一个对象

2. 态射(Morphisms) = 函数(Functions)

态射是对象之间的映射
在编程中,这就是函数

text
const strToLen = (s: string): number => s.length;
// string → number

这个函数就是从对象 string 到对象 number态射

其他例子:

text
const double = (n: number): number => n * 2;        // number → number
const validate = (s: string): boolean => s !== '';  // string → boolean

3. 恒等态射(Identity) = 恒等函数

每个对象必须有一个“不做任何事”的态射,称为恒等态射(Identity Morphism)

在编程中,这就是恒等函数(id)

text
const id = <A>(x: A): A => x;

它满足:

text
id(string) → string
id(number) → number
id(User) → User

无论输入什么,原样返回。

三、范畴的两大定律

要称为“范畴”,必须满足两个基本定律:

1. 恒等律(Identity Law)

对任意态射 f: A → B,有:
f ∘ id_A = f = id_B ∘ f

在代码中:

text
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

在代码中就是函数组合的结合性:

js
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. 类型系统的深层意义

类型不只是防止错误,
它们是范畴中的对象
函数是态射
整个程序是态射的复合网络

六、代码示例:构建一个简单的范畴

text
// 对象:类型(用字符串表示)
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

你刚刚实现了一个微型范畴!

七、工程启示

在库设计中:

  • 提供 idcompose 工具
  • 确保函数纯正
  • 文档化类型转换

在团队协作中:

  • 用“类型即对象,函数即变换”思维沟通
  • 把业务逻辑看作“态射的组合”

结语:范畴论是“抽象的语法”

它不关心你做什么业务,只关心你如何组合。

当你写下:

ts
pipe(
  validate,
  transform,
  save
)

你不是在写代码,
你是在构造一个从 InputSaved 的态射

validatetransformsave 都是从一个类型到另一个类型的箭头。

编程,本质上是范畴中的路径绘制。