看到一遍好文章,与我的想法如出一辙,先转为敬。首先说说我对Monad和promise的理解:

  Monad的这种抽象方式是为了简化程序中不确定状态的判断而提出的,能够让程序员从更高的层次顺序描述程序逻辑的每一个动作,而不必关注每一个动作是否会出现异常,也不必关注第一个动作内是否需要逻辑判断,是否要跳转。haskell趣学指南我翻了好几遍,终于对Monad这个概念有了一点认识,这个抽象是伟大的,它极大提高了程序的可读性,同时降低了开发难度。这里还要推荐一下haskell这门语言,Monad的概念就是haskell提出的,haskell的思想完全不同于传统的oo语言,学习过之后,你的编程世界观会有一个转变,原来程序还能这么写呀,然后回头再写oo语言时,效率会比原来有质的飞跃。

  再说promise,经常写前端的朋友应该对这个东西不陌生,前端语言如js,flash等经常要向后端发送ajax请求,当多个ajax嵌套在一起时,回调函数大坑简直让人崩溃,promise就是为了解决这个问题而发明的。用了promise,从此可以优雅的发送ajax,串行发,并行发,多层嵌套发,想怎么发就怎么发。用了这么长时间的promise,我终于发现,原来promise就是Monad啊。

以下是原文,作者把Haskell趣学指南中的代码翻译成Js,完美阐释了Monad的概念,赞!

Monad 这个概念好难解释, 你可以理解为一个 Lazy 或者是状态未知的盒子. 听起来像是薛定谔猫(估计点进去你会更晕了). 其实就是的, 在你打开这个盒子之前, 你是不知道里面的猫处在那种状态.

Monad 这个黑盒子, 里面到底卖的神马药,我们要打开喝了才知道.

等等, 不是说好要解释 Either 的吗, 嗯嗯, 这里就是在解释 Either. 上节说 Either 是一个 Functor, 可以被 fmap over. 怎么这里又说道黑盒子了? 好吧, Monad 其实也是 Functor. 还记得我说的 Functor 其实是一个带 context 的盒子吗. 而 fmap 使得往盒子里应用函数变换成为了可能.

Either

先来看看 Either 这种类型会干什么事情. Either表示要不是左边就是右边的值, 因此我们可以用它来表示薛定谔猫, 要不是活着, 要不死了. Either 还有个方法:
either

(a -> c) -> (b -> c) -> Either a b -> c

想必你已经对箭头->非常熟了吧.如果前面几章你都跳过了,我再翻译下好了. 这里表示接收函数a->c和函数b->c, 再接收一个 Either, 如果 Either 的值在左边,则使用函数映射a->c, 若值在右边,则应用第二个函数映射b->c.

作为 Monad, 它还必须具备一个方法 '>>='(这个符号好眼熟的说, 看看 haskell 的 logo, 你就知道 Monad 是有多重要), 也就是 bind 方法.

bind 方法的意思很简单, 就是给这个盒子加一个操作, 比如往盒子在加放射性原子,如果猫活着,就是绿巨猫, 如果猫是死的,那就是绿巨死猫.

Left("cat").bind(cat => 'hulk'+cat)
// => Left "hulkcat"
Right("deadcat").bind(cat => 'hulk' + cat)
// => Right "hulkdeadcat"

这有个毛用啊. 表急... 来看个经典例子

走钢索

皮尔斯决定要辞掉他的工作改行试着走钢索。他对走钢索蛮在行的,不过仍有个小问题。就是鸟会停在他拿的平衡竿上。他们会飞过来停一小会儿,然后再飞走。这样的情况在两边的鸟的数量一样时并不是个太大的问题。但有时候,所有的鸟都会想要停在同一边,皮尔斯就失去了平衡,就会让他从钢索上掉下去。

我们这边假设两边的鸟差异在三个之内的时候,皮尔斯仍能保持平衡。

一般解法

首先看看不用 Monad 怎么解

eweda.installTo(this);
var landLeft = eweda.curry(function(n, pole){
return [pole[0]+n, pole[1]];
});
var landRight = eweda.curry(function(n, pole){
return landLeft(n, eweda.reverse(pole));
});
var result = eweda.pipe(landLeft(1), landRight(1), landLeft(2))([0,0]);
console.log(result);
// => [3, 1]

还差一个判断皮尔斯是否掉下来的操作.

var landLeft = eweda.curry(function(n, pole){
if(pole==='dead') return pole;
if(Math.abs(pole[0]-pole[1]) > 3)
return 'dead';
return [pole[0]+n, pole[1]];
});
var landRight = eweda.curry(function(n, pole){
if(pole==='dead') return pole;
return landLeft(n, eweda.reverse(pole));
});
var result = eweda.pipe(landLeft(10), landRight(1), landRight(8))([0,0]);
console.log(result);
// => dead

完整代码


现在来试试用 Either

我们先把皮尔斯放进 Either 盒子里, 这样皮尔斯的状态只有打开 Either 才能看见. 假设 Either Right 是活着, Left 的话皮尔斯挂了.

var land = eweda.curry(function(lr, n, pole){
pole[lr] = pole[lr] + n;
if(Math.abs(pole[0]-pole[1]) > 3) {
return new Left("dead when land " + n + " became " + pole);
}
return new Right(pole);
}); var landLeft = land(0)
var landRight = land(1);

现在落鸟后会返回一个 Either, 要不活着, 要不挂了. 打开盒子的函数可以是这样的

var stillAlive = function(x){
console.log(x)
}
var dead = function(x){
console.log('皮尔斯' + x);
}
either(dead, stillAlive, landLeft(2, [0,0]))

好吧, 好像有一点点像了, 但是这只落了一次鸟, 如果我要落好几次呢. 这就需要实现 Either 的 >>= bind 方法了, 如果你还记得前面实现的 Functor, 这里非常像 :

var Monad = function(type, defs) {
for (name in defs){
type.prototype[name] = defs[name];
}
return type;
};
function Left(value){
this.value = value
}
function Right(value){
this.value=value;
} Monad(Right, {
bind:function(fn){
return fn(this.value)
}
}) Monad(Left, {
bind: function(fn){
return this;
}
})

哦, 对了, either:

either = function(left, right, either){
if(either.constructor.name === 'Right')
return right(either.value)
else
return left(either.value)
}

我们来试试工作不工作.

var walkInLine = new Right([0,0]);
eitherDeadOrNot = walkInLine.bind(landLeft(2))
.bind(landRight(5))
either(dead, stillAlive, eitherDeadOrNot)
// => [2,5]
eitherDeadOrNot = walkInLine.bind(landLeft(2))
.bind(landRight(5))
.bind(landLeft(3))
.bind(landLeft(10)
.bind(landRight(10))) either(dead, stillAlive, eitherDeadOrNot)
// => "皮尔斯dead when land 10 became 15,5"

完整代码

到底有什么用呢, Monad

我们来总结下两种做法有什么区别:
1. 一般做法每次都会检查查尔斯挂了没挂, 也就是重复获得之前操作的 context
2. Monad 不对异常做处理, 只是不停地往盒子里加操作. 你可以看到对错误的处理推到了最后取值的 either.
2. Monad 互相传递的只是盒子, 而一般写法会把异常往下传如"dead", 这样导致后面的操作都得先判断这个异常.

comment 由于是用 JavaScript, pole 不限定类型, 所以这里单纯的用字符串代表 pole 的异常状态. 但如果换成强类型的 Java, 可能实现就没这么简单了.

看来已经优势已经逐步明显了呢, Monad 里面保留了值的 context, 也就是我们对这个 Monad 可以集中在单独的本次如何操作value, 而不用关心 context.

还有一个 Monad 叫做 Maybe, 实际上皮尔斯的

Monad 在 JavaScript 中的应用

你知道 ES6有个新的 类型 Promise 吗, 如果不知道, 想必也听过 jQuery 的 $.ajax吧, 但如果你没听过 promise, 说明你没有认真看过他的返回值:

var aPromise = $.ajax({
url: "https://api.github.com/users/jcouyang/gists"
dataType: 'jsonp'
})
aPromise /***
=> Object { state: .Deferred/r.state(),
always: .Deferred/r.always(),
then: .Deferred/r.then(),
promise: .Deferred/r.promise(),
pipe: .Deferred/r.then(),
done: b.Callbacks/p.add(),
fail: b.Callbacks/p.add(),
progress: b.Callbacks/p.add() }
***/

我们看到返回了好多Deferred类型的玩意, 我们来试试这玩意有什么用

anotherPromise = aPromise.then(_ => _.data.forEach(y=> console.log(y.description)))
/* =>
Object { state: .Deferred/r.state(),
always: .Deferred/r.always(),
then: .Deferred/r.then(),
promise: .Deferred/r.promise(),
pipe: .Deferred/r.then(),
done: b.Callbacks/p.add(),
fail: b.Callbacks/p.add(),
progress: b.Callbacks/p.add() } "connect cisco anyconnect in terminal"
"为什么要柯里化(curry)"
"批量获取人人影视下载链接"
......
*/

看见没有, 他又返回了同样一个东西, 而且传给 then 的函数可以操作这个对象里面的值. 这个对象其实就是 Promise 了. 为什么说这是 Monad 呢, 来试试再写一次走钢丝:

这里我们用的是 ES6 的 Promise, 而不用 jQuery Defered, 记得用 firefox 哦. 另外 eweda 可以这样装

var ewd = document.createElement('script'); ewd.type = 'text/javascript'; ewd.async = true;
ewd.src = 'https://rawgit.com/CrossEye/eweda/master/eweda.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ewd); eweda.installTo(this); //安装到 window 上
var land = curry(function(lr, n, pole){
pole[lr] = pole[lr] + n;
if(Math.abs(pole[0]-pole[1]) > 3) {
return new Promise((resovle,reject)=>reject("dead when land " + n + " became " + pole));
}
return new Promise((resolve,reject)=>resolve(pole));
}); var landLeft = land(0)
var landRight = land(1); Promise.all([0,0])
.then(landLeft(2), _=>_)
.then(landRight(3), _=>_) // => Array [ 2, 3 ]
.then(landLeft(10), _=>_)
.then(landRight(10), _=>_)
.then(_=>console.log(_),_=>console.log(_))
// => "dead when land 10 became 12,3"

这下是不承认 Promise 就是 Monad 了. 原来我们早已在使用这个神秘的 Monad, 再想想 Promise,也没有那么抽象和神秘了.

转评:你造promise就是monad吗的更多相关文章

  1. Promise是Monad吗?

    译者按: 近年来,函数式语言的特性都被其它语言学过去了. 原文: Functional Computational Thinking — What is a monad? 译者: Fundebug 为 ...

  2. 函数式JS: 原来promise是这样的monad

    转载请注明出处: http://hai.li/2017/03/27/prom... 背景 上篇文章 函数式JS: 一种continuation monad推导 得到了一个类似promise的链式调用, ...

  3. Js-函数式编程

    前言 JavaScript是一门多范式语言,即可使用OOP(面向对象),也可以使用FP(函数式),由于笔者最近在学习React相关的技术栈,想进一步深入了解其思想,所以学习了一些FP相关的知识点,本文 ...

  4. Functional Programming without Lambda - Part 2 Lifting, Functor, Monad

    Lifting Now, let's review map from another perspective. map :: (T -> R) -> [T] -> [R] accep ...

  5. 指令式Callback,函数式Promise:对node.js的一声叹息

    原文:Callbacks are imperative, promises are functional: Node's biggest missed opportunity promises 天生就 ...

  6. Monad / Functor / Applicative 浅析

    前言 Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性.这也使得我们学习掌握这门语言变得相对来说更加困 ...

  7. 一个Monad的不严谨介绍

    一个单子(Monad)说白了不过就是自函子范畴上的一个幺半群而已,这有什么难以理解的?* 之前了解了下Monad,后来一段时间没碰,最近研究Parser用到Monad时发现又不懂了.现在重新折腾,趁着 ...

  8. 【响应式编程的思维艺术】 (3)flatMap背后的代数理论Monad

    目录 一. 划重点 二. flatMap功能解析 三. flatMap的推演 3.1 函数式编程基础知识回顾 3.2 从一个容器的例子开始 3.3 Monad登场 3.4 对比总结 3.5 一点疑问 ...

  9. 从函数式编程到Promise

    译者按: 近年来,函数式语言的特性都被其它语言学过去了.JavaScript异步编程中大显神通的Promise,其实源自于函数式编程的Monad! 原文: Functional Computation ...

随机推荐

  1. iptables转发

    需求 将流入服务器的公网IP的80端口的流量全部转发到另一个公网IP(1.2.3.4)的80端口上. 操作 iptables -P FORWARD ACCEPT iptables -t nat -A ...

  2. sin, miss the mark, correct our aim and try again

    Guilt should only be a call to action. When we see that we "missed the mark"(the meaning o ...

  3. JQuery mobile 实例 api

    http://www.w3school.com.cn/jquerymobile/jquerymobile_examples.asp

  4. 7.Mybatis关联表查询(这里主要讲的是一对一和一对多的关联查询)

    在Mybatis中的管理表查询这里主要介绍的是一对一和一对多的关联查询的resultMap的管理配置查询,当然你也可以用包装类来实现.不过这里不说,做关联查询的步骤可以简单的总结为以下的几步: 1.分 ...

  5. Elasticsearch 插件安装

    http://www.cnblogs.com/richaaaard/p/5212044.html

  6. B站运维团队成长的血泪史

    胡凯,bilibili运维负责人,曾经就职于金山软件.金山网络.猎豹移动,负责运维相关工作.Bilibili是国内最大的年轻人潮流文化娱乐社区,银河系知名弹幕视频分享UGC平台.   95后二次元新人 ...

  7. boxes

    boxes [英][bɒksɪz][美][bɑ:ksɪz] n.盒( box的名词复数 ); 一盒; 电视; 小亭; v.把…装入盒[箱,匣]中( box的第三人称单数 ); 拳击;   以上结果来自 ...

  8. CSS样式之语法

    选择符 选择符 {属性1:属性值1;属性2:属性值2} 选择符(selector):指定样式适用的标签,除指定标签外,样式不起作用 属性:样式的关键字 属性值:描述样式的值: 格式:属性与属性之间使用 ...

  9. django 创建项目

    django-admin startproject project-name 启动服务器 python manage.py runserver 0.0.0.0:8000 配置ALLOW_HOST

  10. IOS AFNetworking配置进IOS

    Prefix Header 中填入绝对路径 //PCH 里面加入这个写代码 #ifndef TARGET_OS_IOS #pragma mark ---------- for AFNetwork st ...