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

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

image.png

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


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

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

image.png


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

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


浅尝 Monad



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


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

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


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

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


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

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

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


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


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


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


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


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


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

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


Promise 和 Monad



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

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


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 是函数式编程的浪漫!

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


以上。

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

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


推荐阅读





相关文章
|
算法 数据挖掘
群体遗传学研究荐读丨应知应会(下)
群体遗传学研究荐读丨应知应会(下)
|
算法 Python
群体遗传学研究荐读丨应知应会(上)
群体遗传学研究荐读丨应知应会
|
数据挖掘 区块链
(币安/okex)交易所搬砖套利软件开发源码规则解析
(币安/okex)交易所搬砖套利软件开发源码规则解析
|
搜索推荐 程序员 SEO
程序员怎么接私活:外包众包接单方法!
程序员怎么接私活:外包众包接单方法!
813 0
绩效被打C了!谈谈「绩效考核」背后的逻辑以及潜规则
在新公司度过了一个完整的 Q3 季度,被打了绩效,也给下属打了绩效,感慨颇深。 今天就好好聊聊大厂打工人最最关心的「绩效考核」,谈谈它背后的逻辑以及潜规则,摸清楚了它,你在大厂这片丛林里才能更好的生存下去。
|
Web App开发 移动开发 小程序
“似水无形” - 小程序化
企业前端人机交互的软件技术载体,曾经是PC软件,然后是网页,再后来是App,之后是小程序形态的轻应用。“小程序化”,也许是物联网时代的趋势?但形态不是最根本的,重要的是“可交互的数字内容”,因为我们使用软件的目的是为了它背后的效用。“似水无形”,在什么设备里它就呈现什么形态。
108 0
|
数据挖掘
中国餐馆过程(CRP)
  查如何事先确定聚类簇数目发现的,是对狄利克雷过程的(DP)的一种解释。   假设一个中国餐馆有无限的桌子,第一个顾客到来之后坐在第一张桌子上。第二个顾客来到可以选择坐在第一张桌子上,也可以选择坐在一张新的桌子上,假设第n+1个顾客到来的时候,已经有k张桌子上有顾客了,分别坐了n1,n2,...,nk个顾客,那么第n+1个顾客可以以概率为ni/(\alpha+n)坐在第i张桌子上,ni为第i张桌子上的顾客数;同时有概率为\alpha/(\alpha+n)选取一张新的桌子坐下。
1228 0
|
BI 项目管理
艾伟也谈项目管理,五大绝招 消除项目小组与用户的矛盾
  BI项目实施过程中,会导致用户现有工作量的增加,会对用户现有工作进行重新分配,总之会影响用户的即得利益。在这种情况下,项目小组与用户之间矛盾的增加。虽然说BI系统主要是企业管理者在使用但是这个系统的基石基础数据,则是一线用户所提供的。
980 0
为什么建立自己的交易系统?看明白kinmall归纳的这四点你就懂了
在之前我们的文章提过找时间给大家分享一下,如何构建交易系统。趁这些天行情不错,大家喜笑颜开的环境下,金猫kinmall再送干货给大家,算是锦上添花。
5180 0