编辑
2024-07-22
后端
00
请注意,本文编写于 292 天前,最后修改于 292 天前,其中某些信息可能已经过时。

在范畴论中,函子是范畴间的一类映射。函子也可以解释为小范畴范畴内的态射。

函子首先现身于代数拓扑学,其中拓扑空间的连续映射给出相应的代数对象(如基本群、同调群或上同调群)的代数同态。在当代数学中,函子被用来描述各种范畴间的关系。

函子(Functor)是函数式编程中的一个重要概念,源自于范畴论。在fp范式编程语言中,函子通常指的是一种可以被映射(map)的数据结构。它核心思想是,它提供了一种方式来对容器内的值进行操作,而不改变容器本身。

函子的基本特性:

  1. 映射(Mapping):函子提供了一个 map 方法,该方法接受一个函数作为参数,并将这个函数应用到容器内的每个元素上。

  2. 保持结构:映射操作不会改变容器本身的结构,只会改变容器内的值。

下面我们用js写一个简单的例子

js
class Functor { constructor(value) { this.value = value; } map(fn) { return new Functor(fn(this.value)); } } const f = new Functor(1); const result = f.map(x => x + 1); console.log(result.value); // 输出 2

map返回了一个新的Functor对象,既没有改变原来的对象结构,又完成了范畴间的转换。

现在我们初步了解了什么是函子,总的来说,他有一个map方法,参数是一个纯函数,用于对自身内元素(所有)应用这个纯函数。返回一个新的Functor,而不改变原来的对象。接下来我们来进阶一下。

以下是一些常见的函子

首先是Maybe函子,当Funtor的value是null的时候,map操作无疑是会失败的,会有空指针的问题。

Maybe 函子用于表示一个值可能存在或不存在的情况。它通常有两个子类型:Just 和 Nothing

  • Just:表示值存在的情况。

  • Nothing:表示值不存在的情况。

代码实现如下

js
class Maybe { constructor(value) { this.value = value; } static just(value) { return new Just(value); } static nothing() { return new Nothing(); } map(fn) { return this.value ? Maybe.just(fn(this.value)) : Maybe.nothing(); } } class Just extends Maybe { constructor(value) { super(value); } } class Nothing extends Maybe { constructor() { super(null); } } const maybeValue = Maybe.just(5); const result = maybeValue.map(x => x * 2); console.log(result instanceof Just ? result.value : result); // 输出 10 const maybeNothing = Maybe.nothing(); const resultNothing = maybeNothing.map(x => x * 2); console.log(resultNothing instanceof Nothing ? 'Nothing' : resultNothing.value); // 输出 'Nothing'

还有一种常见的函子是Either函子,Either 函子用于表示两种可能的情况:一种是“右值”(通常表示成功的结果),另一种是“左值”(通常表示错误或失败的原因)。

  • Right:表示成功的结果。

  • Left:表示错误或失败的原因。

Either 函子的一个重要特性是它可以明确地表示错误信息,而不仅仅是简单的“值不存在”。所以,Rust的Result和Either函子是等价的,而另一个最近新兴语言特性Optional,则更接近于Maybe函子,处理一个值为null和非null的情况,如Java,C++,Python都有相关实现。

js
class Either { constructor(value) { this.value = value; } static right(value) { return new Right(value); } static left(value) { return new Left(value); } map(fn) { return this instanceof Right ? Either.right(fn(this.value)) : Either.left(this.value); } } class Right extends Either { constructor(value) { super(value); } } class Left extends Either { constructor(value) { super(value); } } const eitherRight = Either.right(5); const resultRight = eitherRight.map(x => x * 2); console.log(resultRight instanceof Right ? resultRight.value : resultRight.value); // 输出 10 const eitherLeft = Either.left('Error'); const resultLeft = eitherLeft.map(x => x * 2); console.log(resultLeft instanceof Left ? resultLeft.value : resultLeft.value); // 输出 'Error'

还有更多的函子,如IO函子,Reader函子,List函子,可以自行了解。

函子的复合也是函子的重要概念,它允许我们将多个函子组合在一起,形成一个新的函子。这种组合方式类似于函数的复合,即一个函数的输出作为另一个函数的输入。

函子的复合概念

假设我们有两个函子 F 和 G,我们可以通过复合形成一个新的函子 F ∘ G(读作 "F after G" 或 "F composed with G")。这个新的函子将首先应用 G,然后将结果传递给 F。

示例:

js
class Compose { constructor(fg) { this.fg = fg; } static of(fg) { return new Compose(fg); } map(fn) { return Compose.of(this.fg.map(g => g.map(fn))); } } // 示例用法 class Maybe { constructor(value) { this.value = value; } static just(value) { return new Maybe(value); } static nothing() { return new Maybe(null); } map(fn) { return this.value ? Maybe.just(fn(this.value)) : Maybe.nothing(); } } class List { constructor(values) { this.values = values; } static of(values) { return new List(values); } map(fn) { return List.of(this.values.map(fn)); } } const compose = Compose.of(List.of([Maybe.just(1), Maybe.just(2), Maybe.nothing()])); const result = compose.map(x => x * 2); console.log(result); // 输出: Compose { fg: List { values: [ Maybe { value: 2 }, Maybe { value: 4 }, Maybe { value: null } ] } }

函子复合允许我们定义一组函子,将他们组合成一个复杂的数据处理管道

本文作者:yowayimono

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!