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

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

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

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


以上。

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

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


推荐阅读





相关文章
|
3月前
|
搜索推荐
酒吧霸屏系统开发规则逻辑分析
酒吧霸屏软件通过大屏幕显示器或投影设备,实时播放最新的音乐MV、时尚的视觉特效、有趣的互动游戏等多种元素,为顾客带来沉浸式的娱乐体验。这种新型的娱乐展示方式,不仅能够增加酒吧的吸引力和活跃度,还能够提升顾客的消费体验,吸引更多的顾客前来消费。
|
4月前
|
Java Python
编码之舞:从混乱到秩序的旅程
【6月更文挑战第3天】在数字世界的无限可能中,编程不仅仅是一种技术实践,它是一场思维与逻辑的舞蹈,是创意与结构的和谐交响。本文将带领读者穿梭于代码的海洋,探索如何将混沌的想法转化为清晰、高效的程序,揭示那些隐藏在平凡代码行背后的深刻见解和创造性解决方案。
|
Cloud Native 容灾 程序员
三点“揭露”内向技术人如何做好分享?
希望本文能帮助所有内向者发现自身的优势,实现由内而外的成长。
763 22
三点“揭露”内向技术人如何做好分享?
|
数据挖掘 区块链
(币安/okex)交易所搬砖套利软件开发源码规则解析
(币安/okex)交易所搬砖套利软件开发源码规则解析
|
搜索推荐 程序员 SEO
程序员怎么接私活:外包众包接单方法!
程序员怎么接私活:外包众包接单方法!
798 0
绩效被打C了!谈谈「绩效考核」背后的逻辑以及潜规则
在新公司度过了一个完整的 Q3 季度,被打了绩效,也给下属打了绩效,感慨颇深。 今天就好好聊聊大厂打工人最最关心的「绩效考核」,谈谈它背后的逻辑以及潜规则,摸清楚了它,你在大厂这片丛林里才能更好的生存下去。
|
Web App开发 移动开发 小程序
“似水无形” - 小程序化
企业前端人机交互的软件技术载体,曾经是PC软件,然后是网页,再后来是App,之后是小程序形态的轻应用。“小程序化”,也许是物联网时代的趋势?但形态不是最根本的,重要的是“可交互的数字内容”,因为我们使用软件的目的是为了它背后的效用。“似水无形”,在什么设备里它就呈现什么形态。
106 0
|
大数据
零售数据观(一):如何花30分钟成为一个标签设计“达人”
作者简介:铁叫兽,10年+数据相关经验,曾在电信、阿里从事过DBA,数仓,解决方案,目前从事零售行业的解决方案。 序言:是否碰到大量的人力投入基于流程管理的信息化系统建设,也运行了好几年了,同时大数据也热了好几年了,但企业IT部门还是无从下手,既不确信大数据是否可以真的带来业务价值也不清楚从哪着手更容易推动大数据项目落地,本文就是通过“标签”,一种基于具体业务场景但同时又是业务人员看的懂的数据的方式,帮助企业从点做起,循序渐进,让大数据真正落地。
下一篇
无影云桌面