不完整解释 Monad 有什么用

【预警】这篇文章没有详细解释 Monad,我承诺我会抽空写。css

关于我为何本身打脸回来写文章,不想解释太多。只想说掘金真香。数据库

本打算这周末写文章解释下 Monad 的,可是最近比较忙,仍是再拖一会。编程

新工做挑战比较大。第一次遇到这么复杂的业务和开发流程,一开始适应的不是很好。开会全程懵逼,不知作别人在讲什么。最近主要精力仍是要花在熟悉业务和新的工做环境,学习分享上会缓一缓。闭包

先简单介绍一下 Monad 的用处预热一下吧。可能看完这篇你不会全懂,那是由于我没仔细解释,留待下次吧,抱歉了。这里要展现的代码主体部分是个人练习改写,后半部辅助函数和示例是我模仿改写的。app

function IO(effectFn) {
  const __val = effectFn
  const map = fn => IO(() => fn(__val()))
  const performUnsafeIO = __val
  const chain = fn => IO(() => fn(__val()).performUnsafeIO())
  return Object.freeze({
    map,
    chain,
    performUnsafeIO,
  })
}

const curry = fn => (...args) =>
  args.length >= fn.length ? fn(...args) : curry(fn.bind(undefined, ...args))

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))

const map = curry((fn, monad) => monad.map(fn))

const chain = curry((fn, monad) => monad.chain(fn))

const setStyle = curry((sel, props) => IO(() => $(sel).css(props)))

const getItem = key => IO(() => localStorage.getItem(key))

const applyPreferences = compose(
  chain(setStyle('#main')),
  map(JSON.parse),
  getItem
)

applyPreferences('preferences').performUnsafeIO()
复制代码

这个 IO Monad 在一些 ADT 库里面也叫 Effect,它是来处理应用中的做用的。先看示例部分。这个应用的主要功能就是从 localStorage 读取用户的样式偏好,读到以后再改掉页面对应的样式。这个简单例子涉及到两个做用(effects),注意做用和反作用(side effects)是两个不一样的概念。这两个做用是读取数据库和改变 DOM 节点样式属性。函数式编程的一个主要挑战就是把计算和做用分离开来,计算的过程当中不能产生做用。ide

回到代码看是怎样作到的。首先 getItem 函数把根据传入的 key 读取 localStorage 的行为扔进了 IO 函数。IO 函数把这个会产生做用的里层函数存在闭包里,并无当即执行。map 的做用就是,先执行传进 IO 的函数,再把计算结果传进 map 自身接受的回调函数。可是请注意,map 并无当即执行会产生做用的函数,它只是声明了行为。接着,到了最难理解的 chain 函数(若是你理解了 chain 在干什么,你就彻底理解 Monad 了)。chain 接受的回调函数自身也会返回一个 IO,这个时候就不能直接把回调函数执行的结果扔回给 IO 了,否则就是 IO 嵌套 IO,没办法 map 了。因此先把里层 IO 的做用函数执行一遍,再把结果塞回 IO。一样,这里只是声明行为,没有真的执行。函数式编程

程序运行到 applyPreferences("preferences") 的时候,就把应用功能所有描述完了,但只是定义了每一步的计算,还没开始执行指令。到最后一部 performUnsafeIO 的时候,奇迹才会发生,做用才会释放。再回过头看整个程序,是否是以为很干净?无论你有没感觉到,反正我感觉到了……函数

你可能会问:谁 TM 这样子写代码找抽啊!!!学习

其实,RxJS 的原理差很少就是这样的。Observable 就是个 IO Monad。RxJS 里面声明的计算,都是惰性的,只有在最后 subscribe 的时候,计算才会被触发,做用才会被释放。ui

本文示例主要目的是演示,仍是过于简单化。注意 JSON.parse 可能会抛出异常,而抛出异常也属于做用。有一个叫 Maybe 的数据类型专用来解决这类问题。我之前在一篇介绍 Ramda 的文章最后面有演示 Maybe 的用法。

线上 Demo 戳这里

这篇文章也发表在个人中文博客上 Lambda Academy

相关文章
相关标签/搜索