JS `Category Theory` `Profunctor`, `Adjunction`, `Monad Transformers` 在函数式编程

admin
Mon, 21 Jul 2025 17:31:57 +0000
Javascript, category, js, theory

各位靓仔靓女们，晚上好！我是今晚的讲师，江湖人称“代码界的段子手”。今天咱们不聊八卦，不谈人生，就来唠唠函数式编程里那些听起来高大上，实际上也确实有点儿意思的概念：Profunctor（预函子），Adjunction（伴随）和 Monad Transformers（Monad 转换器）。 别害怕，虽然名字有点儿吓人，但保证用最接地气的方式，把它们扒个底朝天，让你听完之后，感觉自己也能对着电脑指点江山，成为朋友圈里最懂 Category Theory (范畴论) 的仔！ 第一部分：Profunctor – 左右逢源的变形金刚 首先，咱们来聊聊 Profunctor。这玩意儿，你可以把它想象成一个“变形金刚”，但它变形的不是汽车飞机，而是函数。更准确地说，Profunctor 是一个接受两个类型参数的类型构造器，并且能对传入的函数进行“左右逢源”的变形。 听起来有点儿绕？没关系，咱们直接上代码： // 定义一个 Profunctor 的接口 class Profunctor { constructor(val) { this.val = val; } static of(val …

继续阅读“JS `Category Theory` `Profunctor`, `Adjunction`, `Monad Transformers` 在函数式编程”

The post JS `Category Theory` `Profunctor`, `Adjunction`, `Monad Transformers` 在函数式编程 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位靓仔靓女们，晚上好！我是今晚的讲师，江湖人称“代码界的段子手”。今天咱们不聊八卦，不谈人生，就来唠唠函数式编程里那些听起来高大上，实际上也确实有点儿意思的概念：Profunctor（预函子），Adjunction（伴随）和 Monad Transformers（Monad 转换器）。

别害怕，虽然名字有点儿吓人，但保证用最接地气的方式，把它们扒个底朝天，让你听完之后，感觉自己也能对着电脑指点江山，成为朋友圈里最懂 Category Theory (范畴论) 的仔！

第一部分：Profunctor – 左右逢源的变形金刚

首先，咱们来聊聊 Profunctor。这玩意儿，你可以把它想象成一个“变形金刚”，但它变形的不是汽车飞机，而是函数。更准确地说，Profunctor 是一个接受两个类型参数的类型构造器，并且能对传入的函数进行“左右逢源”的变形。

听起来有点儿绕？没关系，咱们直接上代码：

// 定义一个 Profunctor 的接口
class Profunctor {
  constructor(val) {
    this.val = val;
  }

  static of(val) {
    return new Profunctor(val);
  }

  map(f) {
    throw new Error("Method 'map(f)' must be implemented.");
  }

  contramap(f) {
    throw new Error("Method 'contramap(f)' must be implemented.");
  }

  dimap(f, g) {
    return this.contramap(f).map(g);
  }
}

// 举个例子：一个简单的函数包装器
class FunctionWrapper extends Profunctor {
  constructor(fn) {
    super(fn);
  }

  static of(fn) {
    return new FunctionWrapper(fn);
  }

  map(f) {
    // f: b -> c
    // this.val: a -> b
    return new FunctionWrapper((a) => f(this.val(a))); // a -> c
  }

  contramap(f) {
    // f: c -> a
    // this.val: a -> b
    return new FunctionWrapper((c) => this.val(f(c))); // c -> b
  }
}

// 使用例子
const toUpper = (str) => str.toUpperCase();
const getLength = (str) => str.length;

const wrappedToUpper = FunctionWrapper.of(toUpper); // FunctionWrapper(toUpper)
const wrappedGetLength = FunctionWrapper.of(getLength); // FunctionWrapper(getLength)

// 使用 map 变形
const toUpperThenGetLength = wrappedToUpper.map(getLength); // FunctionWrapper( (str) => getLength(toUpper(str)) )
console.log(toUpperThenGetLength.val("hello")); // 输出 5

// 使用 contramap 变形
const getFirstChar = (str) => str[0];
const getAsciiCode = (char) => char.charCodeAt(0);
const wrappedGetAsciiCode = FunctionWrapper.of(getAsciiCode); // FunctionWrapper(getAsciiCode)

const getFirstCharThenGetAsciiCode = wrappedGetAsciiCode.contramap(getFirstChar); // FunctionWrapper( (str) => getAsciiCode(getFirstChar(str)) )
console.log(getFirstCharThenGetAsciiCode.val("world")); // 输出 119 (w 的 ASCII 码)

// 使用 dimap 变形
const stringToInt = (str) => parseInt(str, 10);
const intToString = (num) => num.toString();
const wrappedStringToInt = FunctionWrapper.of(stringToInt);

const stringToIntThenToString = wrappedStringToInt.dimap(intToString, intToString);
console.log(stringToIntThenToString.val("123")); // 输出 "123"

在这个例子中，FunctionWrapper 就是一个 Profunctor。它包装了一个函数，并且提供了 map 和 contramap 方法。

map 方法接受一个函数 f: b -> c，然后将 this.val: a -> b 变形为 a -> c。 你可以理解为对函数的输出进行变形。

contramap 方法接受一个函数 f: c -> a，然后将 this.val: a -> b 变形为 c -> b。 你可以理解为对函数的输入进行变形。

dimap 方法就是一个组合，先 contramap 再 map。

Profunctor 的应用场景

Profunctor 在实际开发中，可以用来：

类型转换： 就像上面的例子，我们可以用 map 和 contramap 来灵活地转换函数的输入输出类型。

中间件： 可以把 Profunctor 看作一个中间件，在函数执行前后添加一些处理逻辑。

数据验证： 可以利用 contramap 来对输入数据进行预处理，确保数据的有效性。

第二部分：Adjunction – 互为表里的好基友

接下来，咱们来聊聊 Adjunction。这玩意儿，你可以把它想象成一对“好基友”，它们之间存在着一种特殊的“默契”，可以互相转换，互为表里。

在范畴论中，Adjunction 指的是两个函子（Functor）之间的关系。 如果存在两个函子 F: C -> D 和 G: D -> C，以及两个自然变换 η: IdC -> G F (unit) 和 ε: F G -> IdD (counit)， 满足某些条件（称为三角形恒等式），那么我们就说 F 是 G 的左伴随，G 是 F 的右伴随。

听起来又有点儿绕？没关系，咱们先简化一下，用代码来感受一下：

// 假设我们有两个函子，一个是数组，一个是单例
// 数组函子
const ArrayFunctor = {
  map: (f, arr) => arr.map(f),
  of: (x) => [x],
};

// 单例函子
const SingletonFunctor = {
  map: (f, x) => f(x),
  of: (x) => x,
};

// 假设我们有一个函数，可以将一个值转换为一个数组
const toArray = (x) => [x];

// 假设我们有一个函数，可以将一个数组转换为一个值（取第一个元素）
const fromArray = (arr) => arr[0];

// 那么，`toArray` 和 `fromArray` 就构成了一个 Adjunction

// 我们可以通过 `toArray` 将一个值转换为一个数组，然后再通过 `fromArray` 将数组转换回原来的值
const value = 10;
const array = toArray(value); // [10]
const originalValue = fromArray(array); // 10

// 也可以通过 `fromArray` 将一个数组转换为一个值，然后再通过 `toArray` 将值转换回原来的数组
const array2 = [20];
const value2 = fromArray(array2); // 20
const originalArray = toArray(value2); // [20]

// 这就是 Adjunction 的基本思想：两个函子之间存在着一种可以互相转换的关系

在这个简化的例子中，toArray 和 fromArray 就像一对好基友，它们可以互相转换，互为表里。

Adjunction 的应用场景

Adjunction 在实际开发中，可以用来：

类型转换： 就像上面的例子，我们可以用 Adjunction 来进行类型之间的转换。

状态管理： 在状态管理库中，可以使用 Adjunction 来将状态转换为可观察对象，然后再将可观察对象转换回状态。

数据库查询： 可以使用 Adjunction 来将查询转换为 SQL 语句，然后再将 SQL 语句执行结果转换为数据对象。

第三部分：Monad Transformers – Monad 的变形金刚 Plus

最后，咱们来聊聊 Monad Transformers。这玩意儿，你可以把它想象成 Monad 的“变形金刚 Plus”，它可以让 Monad 拥有更多的能力，就像给变形金刚装上了翅膀，让它能飞得更高，跑得更快。

Monad Transformers 是一种组合 Monad 的技术。 它可以让你在一个 Monad 的基础上，叠加其他的 Monad，从而获得更强大的功能。

听起来还是有点儿绕？没关系，咱们先来回顾一下 Monad：

Monad 是一个具有 of (也叫 return 或 unit) 和 chain (也叫 flatMap 或 bind) 方法的类型构造器。 它可以用来处理副作用，例如：

Maybe Monad： 处理空值

Either Monad： 处理错误

IO Monad： 处理输入输出

但是，如果我们需要同时处理空值和错误呢？ 这时候，Monad Transformers 就派上用场了。

// 定义一个 Maybe Monad
class Maybe {
  constructor(val) {
    this.val = val;
  }

  static of(val) {
    return new Maybe(val);
  }

  map(f) {
    return this.val == null ? Maybe.of(null) : Maybe.of(f(this.val));
  }

  chain(f) {
    return this.val == null ? Maybe.of(null) : f(this.val);
  }
}

// 定义一个 Either Monad
class Either {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  static of(right) {
    return new Either(null, right);
  }

  static left(left) {
    return new Either(left, null);
  }

  map(f) {
    return this.right == null ? this : Either.of(f(this.right));
  }

  chain(f) {
    return this.right == null ? this : f(this.right);
  }
}

// 定义一个 MaybeT Monad Transformer
class MaybeT {
  constructor(monad) {
    this.monad = monad;
  }

  static lift(monad) {
    return new MaybeT(monad);
  }

  static of(val) {
    return new MaybeT(Either.of(Maybe.of(val)));
  }

  map(f) {
    return new MaybeT(this.monad.map(maybe => maybe.map(f)));
  }

  chain(f) {
    return new MaybeT(this.monad.chain(maybe => maybe.val == null ? Either.of(Maybe.of(null)) : f(maybe.val).monad));
  }
}

// 使用例子
const divide = (x, y) => (y === 0 ? Either.left("Division by zero") : Either.of(x / y));

const safeDivide = (x, y) => divide(x, y).map(result => (result > 10 ? Maybe.of(null) : Maybe.of(result)));

const result = safeDivide(20, 2); // Either.of(Maybe.of(null))  由于结果大于10，被 Maybe Monad 处理成 null
const result2 = safeDivide(20, 0); // Either.left("Division by zero")  由于除数为 0，被 Either Monad 处理成错误

console.log(result);
console.log(result2);

在这个例子中，我们定义了一个 MaybeT Monad Transformer。 它可以将 Maybe Monad 叠加到其他的 Monad 上，从而让我们可以在处理其他副作用的同时，也处理空值。

Monad Transformers 的应用场景

Monad Transformers 在实际开发中，可以用来：

组合多个副作用： 就像上面的例子，我们可以用 Monad Transformers 来组合多个副作用，例如：空值、错误、状态、IO 等。

构建复杂的应用程序： 可以使用 Monad Transformers 来构建复杂的应用程序，例如：Web 应用程序、游戏引擎等。

总结

今天咱们聊了 Profunctor、Adjunction 和 Monad Transformers 这三个概念。 虽然它们听起来有点儿高大上，但实际上，它们都是函数式编程中非常有用的工具。

Profunctor： 可以用来对函数进行“左右逢源”的变形。

Adjunction： 可以用来表示两个函子之间的“好基友”关系。

Monad Transformers： 可以用来组合多个 Monad，从而获得更强大的功能。

希望通过今天的讲解，大家对这三个概念有了更深入的理解。 记住，学习函数式编程，就像学习一门新的语言，需要不断地练习和实践。 只要坚持下去，你也能成为函数式编程的高手！

好了，今天的讲座就到这里。 感谢大家的聆听！ 如果有什么问题，欢迎随时提问。 我们下期再见！