【响应式编程的思维艺术】 (3)flatMap背后的代数理论Monad
本文是Rxjs 响应式编程-第二章:序列的深入研究这篇文章的学习笔记。
示例代码托管在:http://www.github.com/dashnowords/blogs
更多博文:《大史住在大前端》目录

一. 划重点
文中使用到的一些基本运算符:
map-映射filter-过滤reduce-有限列聚合scan-无限列聚合flatMap-拉平操作(重点)catch-捕获错误retry-序列重试from-生成可观测序列range-生成有限的可观测序列interval-每隔指定时间发出一次顺序整数distinct-去除出现过的重复值
建议自己动手尝试一下,记住就可以了,有过lodash使用经验的开发者来说并不难。
二. flatMap功能解析
原文中在http请求拿到获取到数据后,最初使用了forEach实现了手动流程管理,于是原文提出了优化设想,试图探究如何依赖响应式编程的特性将手动的数据加工转换改造为对流的转换,好让最终的消费者能够拿到直接可用的数据,而不是得到一个响应后手动进行很多后处理。在代码层面需要解决的问题就是,如何在不使用手动遍历的前提下将一个有限序列中的数据逐个发给订阅者,而不是一次性将整个数据集发过去。
假设我们现在并不知道有flatMap这样一个可以使用的方法,那么先来做一些尝试:
var quakes = Rx.Observable.create(function(observer) {
//模拟得到的响应流
var response = {
features:[{
earth:1
},{
earth:2
}],
test:1
}
/* 最初的手动遍历代码
var quakes = response.features;
quakes.forEach(function(quake) {
observer.onNext(quake);
});*/
observer.onNext(response);
})
//为了能将features数组中的元素逐个发送给订阅者,需要构建新的流
.map(dataset){
return Rx.Observable.from(dataset.features)
}
当我们订阅quakes这个事件流的时候,每次都会得到另一个Observable,它是因为数据源经过了映射变换,从数据变成了可观测对象。那么为了得到最终的序列值,就需要再次订阅这个Observable,这里需要注意的是可观测对象被订阅前是不启动的,所以不用担心它的时序问题。
quakes.subscribe(function(data){
data.subscribe(function(quake){
console.log(quake);
})
});
如果将Observable看成一个盒子,那么每一层盒子只是实现了流程控制功能性的封装,为了取得真正需要使用的数据,最终的订阅者不得不像剥洋葱似的通过subscribe一层层打开盒子拿到最里面的数据,这样的封装性对于数据在流中的传递具有很好的隔离性,但是对最终的数据消费者而言,却是一件很麻烦的事情。
这时flatMap运算符就派上用场了,它可以将冗余的包裹除掉,从而在主流被订阅时直接拿到要使用的数据,从大理石图来直观感受一下flatMap:

乍看之下会觉得它和merge好像是一样的,其实还是有一些区别的。merge的作用是将多个不同的流合并成为一个流,而上图中A1,A2,A3这三个流都是当主流A返回数据时新生成的,可以将他们想象为A的支流,如果你想在支流里捞鱼,就需要在每个支流里布网,而flatMap相当于提供了一张大网,将所有A的支流里的鱼都给捞上来。
所以在使用了flatMap后,就可以直接在一级订阅中拿到需要的数据了:
var quakes = Rx.Observable.create(function(observer) {
var response = {
features:[{
earth:1
},{
earth:2
}],
test:1
}
observer.onNext(response);
}).flatMap((data)=>{
return Rx.Observable.from(data.features);
});
quakes.subscribe(function(quake) {
console.log(quake)
});
三. flatMap的推演
3.1 函数式编程基础知识回顾
如果本节的基本知识你尚不熟悉,可以通过javascript基础修炼(8)——指向FP世界的箭头函数这篇文章来简单回顾一下函数式编程的基本知识,然后再继续后续的部分。
/*map运算符的作用
*对所有容器类而言,它相当于打开容器,进行操作,然后把容器再盖上。
*Container在这里只是一个抽象定义,为了看清楚它对于容器中包含的值意味着什么。
*你会发现它其实就是Observable的抽象原型。
*/
Container.prototype.map = function(f){
return Container.of(f(this.__value))
}
//基本的科里化函数
var curry = function(fn){
args = [].slice.call(arguments, 1);
return function(){
[].push.apply(args, arguments);
return fn.apply(this, args);
}
}
//map pointfree风格的map运算符
var map = curry(function(f, any_functor_at_all) {
return any_functor_at_all.map(f);
});
/*compose函数组合方法
*运行后返回一个新函数,这个函数接受一个参数。
*函数科里化的基本应用,也是函数式编程中运算管道构建的基本方法。
*/
var compose = function (f, g) {
return function (x) {
return f(g(x));
}
};
/*IO容器
*一个简单的Container实现,用来做流程管理
*这里需要注意,IO实现的作用是函数的缓存,且总是返回新的IO实例
*可以看做一个简化的Promise,重点是直观感受一下它作为函数的
*容器是如何被使用的,对于理解Observable有很大帮助
*/
var IO = function(f) {
this.__value = f;
}
IO.of = function(x) {
return new IO(function() {
return x;
});
}
IO.prototype.map = function(f) {
return new IO(compose(f, this.__value));
}
如果上面的基本知识没有问题,那么就继续。
3.2 从一个容器的例子开始
现在来实现这样一个功能,读入一个文件的内容,将其中的a字符全部换成b字符,接着存入另一个文件,完成后在控制台输出一个消息,为了更明显地看到数据容器的作用,我们使用同步方法并将其包裹在IO容器中,然后利用函数式编程:
var fs = require('fs');
//读取文件
var readFile = (filename)=>IO.of(fs.readFileSync(filename,'utf-8'));
//转换字符
var transContent = (content)=>IO.of((content)=>content.replace('a','b'));
//写入字符串
var writeFile = (content)=>IO.of(fs.writeFileSync('dest.txt',content));
当具体的函数被IO容器包裹起来而实现延迟执行的效果时,就无法按原来的方式使用compose( )运算符直接对功能进行组合,因为readFile函数运行时的输出结果(一个io容器实例)和transContent函数需要的参数类型(字符串)不再匹配,在不修改原有函数定义的前提下,函数式编程中采用的做法是使用map操作符来预置一个参数:
/*
*map(transContent)是一个高阶函数,它的返回函数就可以接收一个容器实例,
*并对容器中的内容执行map操作。
*/
var taskStep12 = compose(map(transContent), readFile);
这里比较晦涩,涉及到很多功能性函数的嵌套,建议手动推导一下taskStep12这个变量的值,它的结构是这样一种形式:
io{
__value:io{
__value:someComposedFnExpression
}
}
如果试图一次性将所有的步骤组合在一起,就需要采用下面的形式:
var task = compose(map(map(writeFile)),map(transContent),readFile);
//组合后的task形式就是
//io{io{io{__value:someComposedFnExpression}}}
问题已经浮出水面了,每多加一个针对容器操作的步骤,书写时就需要多包裹一层map,而运行时就需要多进入一层才能触及组合好的可以实现真正功能的函数表达式,真的是很麻烦。
【响应式编程的思维艺术】 (3)flatMap背后的代数理论Monad的更多相关文章
- 【响应式编程的思维艺术】 (2)响应式Vs面向对象
目录 一. 划重点 二. 面向对象编程实例 2.1 动画的基本编程范式 2.2 参考代码 2.3 小结 三. 响应式编程实现 四. 差异对比 4.1 编程理念差异 4.2 编程体验差异 4.3 数学思 ...
- 【响应式编程的思维艺术】 (5)Angular中Rxjs的应用示例
目录 一. 划重点 二. Angular应用中的Http请求 三. 使用Rxjs构建Http请求结果的处理管道 3.1 基本示例 3.2 常见的操作符 四. 冷热Observable的两种典型场景 4 ...
- 【响应式编程的思维艺术】 (1)Rxjs专题学习计划
目录 一. 响应式编程 二. 学习路径规划 一. 响应式编程 响应式编程,也称为流式编程,对于非前端工程师来说,可能并不是一个陌生的名词,它是函数式编程在软件开发中应用的延伸,如果你对函数式编程还没有 ...
- 浅谈Spring 5的响应式编程
这篇使用Spring 5进行响应式编程的入门文章展示了你现在可以使用的一些新的non-blocking, asynchronous.感谢优锐课老师给予的指导! 近年来,由于响应式编程能够以声明性的方式 ...
- Rx系列---响应式编程
Rx是ReactiveX的简称,翻译过来就是响应式编程 首先要先理清这么一个问题:Rxjava和我们平时写的程序有什么不同.相信稍微对Rxjava有点认知的朋友都会深深感受到用这种方式写的程序和我们一 ...
- iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好
转载: iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好 内容来自stack overflow的一个回答:ReactiveCocoa vs RxSwift – pros an ...
- 响应式编程笔记三:一个简单的HTTP服务器
# 响应式编程笔记三:一个简单的HTTP服务器 本文我们将继续前面的学习,但将更多的注意力放在用例和编写实际能用的代码上面,而非基本的APIs学习. 我们会看到Reactive是一个有用的抽象 - 对 ...
- 1小时让你掌握响应式编程,并入门Reactor
我看同步阻塞 “你知道什么是同步阻塞吗”,当然知道了.“那你怎么看它呢”,这个... 在同步阻塞的世界里,代码执行到哪里,数据就跟到哪里.如果数据很慢跟不上来,代码就停在那里等待数据的到来,然后再带着 ...
- Spring 5 响应式编程
要点 Reactor 是一个运行在 Java8 之上的响应式流框架,它提供了一组响应式风格的 API 除了个别 API 上的区别,它的原理跟 RxJava 很相似 它是第四代响应式框架,支持操作融合, ...
随机推荐
- vue常用属性
关键词:filters | 自定义过滤器 (首字母大写) <p>{{ msg | capitalize }}</p> filters: { capitalize: funct ...
- centos7下搭建高匿HTTP代理
一.一般适用情况1.两台都有外网IP,一台服务器请求资源通过另外一个服务器,本文重点讲第一种.2.两台服务器,其中一台服务器只有内网IP,另外一台服务器有公网和内网IP. 二.前提 # 确认服务器端i ...
- 十八、泛型 l 注解 l Servlet3.0 l 动态代理 l 类加载器基础加强
l 泛型 l 注解 l Servlet3.0 l 动态代理 l 类加载器 泛型 1 回顾泛型类 泛型类:具有一个或多个泛型变量的类被称之为泛型类. public class A<T> { ...
- Java中不定项参数(可变参数)的使用
Java1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理. 注意事项: 1)不定项参数必须放在参数列表最后一个. 2)不定项参数只能有一个(多 ...
- return和throw某些特性相似
拷贝构造函数的调用拷贝构造函数会在以下三中情况下被调用(1)当类的一个对象去初始化该类的另一个对象时 int main(){ Point a(1,2); Point b(a);//用对象a初始化对象b ...
- [译文]Domain Driven Design Reference(六)—— 提炼战略设计
本书是Eric Evans对他自己写的<领域驱动设计-软件核心复杂性应对之道>的一本字典式的参考书,可用于快速查找<领域驱动设计>中的诸多概念及其简明解释. 其它本系列其它文章 ...
- 贪心算法----区间选点问题(POJ1201)
题目: 题目的大致意思是,给定n个闭区间,并且这个闭区间上的点都是整数,现在要求你使用最少的点来覆盖这些区间并且每个区间的覆盖的点的数量满足输入的要求点覆盖区间的数量. 输入: 第一行输入n,代表n个 ...
- Python 爬虫入门(一)——爬取糗百
爬取糗百内容 GitHub 代码地址https://github.com/injetlee/Python/blob/master/qiubai_crawer.py 微信公众号:[智能制造专栏],欢迎关 ...
- [Swift]LeetCode122. 买卖股票的最佳时机 II | Best Time to Buy and Sell Stock II
Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...
- [Swift]LeetCode143. 重排链表 | Reorder List
Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You may not mod ...