前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?请说人话!!

【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?请说人话!!

作者头像
掘金安东尼
发布2022-09-19 11:09:54
1K0
发布2022-09-19 11:09:54
举报
文章被收录于专栏:掘金安东尼

theme: awesome-green

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动 本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

起初本瓜看到【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?这句话的时候,还以为自己在看量子力学的量子纠缠相关内容,单子、函子、粒子、玻色子、费米子、绝绝子。。。

正好最近又看到一篇《怎样理解“范畴”?》,解释 “范畴” 都这么费劲?表示脑细胞已经不够用了。。。

至于 “幺半群”?是打麻将吗。。。

image.png
image.png

好家伙~ 最后,你告诉我这句话是关于函数式编程 Monad 的解释,牛你是真滴牛!

  • 怕生词概念的同学先别慌,先告诉你 Monad 和 Promise 很像,增点亲切感;😁

浅尝 Monad

在函数式编程中我们一直强调:纯函数、纯函数、纯函数!无副作用,无副作用,无副作用!

但是,要求总写没有任何副作用的纯函数是几乎不可能的;

HTTP 请求、修改函数外的数据、输出数据到屏幕或控制台、DOM查询/操作、Math.random()、获取当前时间等,这些操作都会使函数产生副作用,导致我们跟踪数据状态困难、代码不易读;

又但是!我们即使不能一直写纯纯的纯函数,不过,尽可能把这些副作用操作放在最后去执行(延迟处理、惰性处理),这也是函数式编程书写纯函数原则之一!

而实现这种做法靠的就是 Monad!

直接上代码,看看 Monad 在实际应用中是怎么写的:

代码语言:javascript
复制
var fs = require("fs");

// 纯函数,传入 filename,返回 Monad 对象
var readFile = function (filename) {
  // 副作用函数:读取文件
  const readFileFn = () => {
    return fs.readFileSync(filename, "utf-8");
  };
  return new Monad(readFileFn);
};

// 纯函数,传入 x,返回 Monad 对象
var print = function (x) {
  // 副作用函数:打印日志
  const logFn = () => {
    console.log(x);
    return x;
  };
  return new Monad(logFn);
};

// 纯函数,传入 x,返回 Monad 对象
var tail = function (x) {
  // 副作用函数:返回最后一行的数据
  const tailFn = () => {
    return x[x.length - 1];
  };
  return new Monad(tailFn);
};

// 链式操作文件
const monad = readFile("./xxx.txt").bind(tail).bind(print);
// 执行到这里,整个操作都是纯的,因为副作用函数一直被包裹在 Monad 里,并没有执行
monad.value(); // 执行副作用函数

我们用 Monad 将包含副作用函数得操作进行封装,到绑定链式操作的时候,都并没有执行任何副作用操作;

直到最后,调用 monad.value() 才执行了这些副作用操作;

在外界看来,被 Monad 函数包裹住含副作用的函数,根本就和纯函数是一样一样的,因为:

你无法知道一间黑色的房间里面有没有一只黑色的猫;

image.png
image.png

在编程开发中,尤其是多人协作中,一个数据要经过各种计算、加入各种逻辑、进行不同线路的变异,最后呈现给消费方;

这个数据的链路越长(多计算)、越多(多分支)、越复杂(多异步),数据的元信息越容易丢失,就像一句话,经过不同人的不同方式转述后,会变得和初始意义相差甚远;

我们试图将计算(函子)和业务输出(链式操作)剥离开来,会让这个“转述”过程更准确、清晰;

wiki 中 Monad

没错,上一小节中的 Monad 只说了它的应用示例,此小 bar 来看看它在 wiki 中的【超干】定义:

单子由 3 个部分组成:

  • 类型构造子 M,建造一个单子类型M T
  • 类型转换子,经常叫做unitreturn,将一个对象x嵌入到单子中: unit(x) :: T -> M T
  • 组合子,典型的叫做bind约束变量的那个bind),并表示为中缀算子>>=,去包装一个单体变量,接着把它插入到一个单体函数/表达式之中,结果为一个新的单体值: (mx >>= f) :: (M T, T -> M U) -> M U

同时,这 3 个组成部分还需遵循 3 个定律:

  • unit是bind的左单比特unit(a) >>= λx -> f(x) ↔ f(a)
  • unit也是bind的右单比特: ma >>= λx -> unit(x) ↔ ma
  • bind本质上符合结合律ma >>= λx -> (f(x) >>= λy -> g(y)) ↔ (ma >>= λx -> f(x)) >>= λy -> g(y)
image.png
image.png

没看懂?确实难懂!本瓜好奇:当我不懂 A 时,有人用 A` 来解释 A,但我又不懂 A`,然后再用 A_ 来解释 A`,还是没懂,之后,再用 A/ 、A·、A+ ......来一层套一层解释,当这个解释线拉的足够长的时候,是否还能做到:有效解释?🐶

可以直接这样理解:Monad 是一种特殊的数据结构,它能把值进行包装,然后链接执行;王垠在《对函数式语言的误解》中准确了描述了 Monad 本质:

Monad 本质是使用类型系统的“重载”(overloading),把这些多出来的参数和返回值,掩盖在类型里面。这就像把乱七八糟的电线塞进了接线盒似的,虽然表面上看起来清爽了一些,底下的复杂性却是不可能消除的。

所以,底下的复杂性是自然。

Promise 和 Monad

我们尝试用 JS 来模拟最基本的 Monad:

代码语言:javascript
复制
class Monad {
  value = "";
  // 构造函数
  constructor(value) {
    this.value = value;
  }
  // unit,把值装入 Monad 构造函数中
  unit(value) {
    this.value = value;
  }
  // bind,把值转换成一个新的 Monad
  bind(fn) {
    return fn(this.value);
  }
}

// 满足 x-> M(x) 格式的函数
function add1(x) {
  return new Monad(x + 1);
}
// 满足 x-> M(x) 格式的函数
function square(x) {
  return new Monad(x * x);
}

// 接下来,我们就能进行链式调用了
const a = new Monad(2)
     .bind(square)
     .bind(add1);
     //...

console.log(a.value === 5); // true

那为什么我们最开始说 Monad 和 Promise 很像呢?

可以看到,确实很像:

  • Promise 也是构造函数;
  • Promise.Resolve ,相当于 Monad unit,用于包装返回值;
  • Promise.prototype.then 相当于 Monad bind,用于链接执行;

Promise 等效于把函数进行包装,Promise.resolve 等效于把这个包装进行拆开,将为一个普通的值;

不过,Promise 不都是 Monad,示例🌰

image.png
image.png

Promise.resolve 传入的是 Promise.resovle(1) 这个 Promise 对象时,已经做了计算,p.then 失效;

关于 Promise 和 Monad 再引用一个很棒的解释(建议重点阅读):

纯函数不能有副作用,所以无法与外部进行 IO 操作,不能存在 a -> IO 或 IO -> a 这种操作,必须为 IO -> IO(Promise -> Promise),也就是必须为「自函子」,async 函数中都是自函子映射,也就是一个「自函子范畴」,那么相对的「幺半群」就是Promise了。

阶段小结

函数式编程中,处处都是惰性思维的体现; Monad 也是惰性计算的实践之一;至于标题中的这句话:【单子】说白了不过就是【自函子范畴】上的一个【幺半群】而已?咱们也用惰性思维去思考:现在很难理解,那我是必须要现在去理解吗?如果不是,那就放到后面需要去理解的时候再去理解吧~~ 不过至少,也要勾勒一下 Monad 和 Promise 关系的大致轮廓;Promise 是 JS 人的浪漫!Monad 是函数式编程的浪漫!

后续还会带来各类单子介绍,建议结合专栏内容,联系前后食用~

以上。

撰文不易,点赞鼓励👍👍👍

我是掘金安东尼,公众号同名,输出暴露输入,技术洞见生活,再会!

推荐阅读

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • theme: awesome-green
  • 浅尝 Monad
  • wiki 中 Monad
  • Promise 和 Monad
  • 阶段小结
  • 推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档