亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

【響應(yīng)式編程的思維藝術(shù)】 (3)flatMap背后的代數(shù)理論Monad

MorePainMoreGain / 693人閱讀

摘要:本文是響應(yīng)式編程第二章序列的深入研究這篇文章的學(xué)習(xí)筆記。函數(shù)科里化的基本應(yīng)用,也是函數(shù)式編程中運(yùn)算管道構(gòu)建的基本方法。四資料參考函數(shù)式編程指南

本文是Rxjs 響應(yīng)式編程-第二章:序列的深入研究這篇文章的學(xué)習(xí)筆記。

示例代碼托管在:http://www.github.com/dashnowords/blogs

更多博文:《大史住在大前端》目錄

一. 劃重點(diǎn)

文中使用到的一些基本運(yùn)算符:

map-映射

filter-過(guò)濾

reduce-有限列聚合

scan-無(wú)限列聚合

flatMap-拉平操作(重點(diǎn))

catch-捕獲錯(cuò)誤

retry-序列重試

from-生成可觀測(cè)序列

range-生成有限的可觀測(cè)序列

interval-每隔指定時(shí)間發(fā)出一次順序整數(shù)

distinct-去除出現(xiàn)過(guò)的重復(fù)值

建議自己動(dòng)手嘗試一下,記住就可以了,有過(guò)lodash使用經(jīng)驗(yàn)的開(kāi)發(fā)者來(lái)說(shuō)并不難。原文中使用flatMap轉(zhuǎn)換序列時(shí)有一處應(yīng)該是手誤:

二. flatMap功能解析

原文中在http請(qǐng)求拿到獲取到數(shù)據(jù)后,最初使用了forEach實(shí)現(xiàn)了手動(dòng)流程管理,于是原文提出了優(yōu)化設(shè)想,試圖探究如何依賴(lài)響應(yīng)式編程的特性將手動(dòng)的數(shù)據(jù)加工轉(zhuǎn)換改造為對(duì)流的轉(zhuǎn)換,好讓最終的消費(fèi)者能夠拿到直接可用的數(shù)據(jù),而不是得到一個(gè)響應(yīng)后手動(dòng)進(jìn)行很多后處理。在代碼層面需要解決的問(wèn)題就是,如何在不使用手動(dòng)遍歷的前提下將一個(gè)有限序列中的數(shù)據(jù)逐個(gè)發(fā)給訂閱者,而不是一次性將整個(gè)數(shù)據(jù)集發(fā)過(guò)去。

假設(shè)我們現(xiàn)在并不知道有flatMap這樣一個(gè)可以使用的方法,那么先來(lái)做一些嘗試:

var quakes = Rx.Observable.create(function(observer) {
    //模擬得到的響應(yīng)流
    var response = { 
       features:[{
        earth:1
       },{
        earth:2
       }],
       test:1
    }
/*  最初的手動(dòng)遍歷代碼  
    var quakes = response.features;
        quakes.forEach(function(quake) {
            observer.onNext(quake);
        });*/

    observer.onNext(response);
})
//為了能將features數(shù)組中的元素逐個(gè)發(fā)送給訂閱者,需要構(gòu)建新的流
.map(dataset){
    return Rx.Observable.from(dataset.features)
}

當(dāng)我們訂閱quakes這個(gè)事件流的時(shí)候,每次都會(huì)得到另一個(gè)Observable,它是因?yàn)閿?shù)據(jù)源經(jīng)過(guò)了映射變換,從數(shù)據(jù)變成了可觀測(cè)對(duì)象。那么為了得到最終的序列值,就需要再次訂閱這個(gè)Observable,這里需要注意的是可觀測(cè)對(duì)象被訂閱前是不啟動(dòng)的,所以不用擔(dān)心它的時(shí)序問(wèn)題。

quakes.subscribe(function(data){
    data.subscribe(function(quake){
        console.log(quake);
    })
});

如果將Observable看成一個(gè)盒子,那么每一層盒子只是實(shí)現(xiàn)了流程控制功能性的封裝,為了取得真正需要使用的數(shù)據(jù),最終的訂閱者不得不像剝洋蔥似的通過(guò)subscribe一層層打開(kāi)盒子拿到最里面的數(shù)據(jù),這樣的封裝性對(duì)于數(shù)據(jù)在流中的傳遞具有很好的隔離性,但是對(duì)最終的數(shù)據(jù)消費(fèi)者而言,卻是一件很麻煩的事情。

這時(shí)flatMap運(yùn)算符就派上用場(chǎng)了,它可以將冗余的包裹除掉,從而在主流被訂閱時(shí)直接拿到要使用的數(shù)據(jù),從大理石圖來(lái)直觀感受一下flatMap:

乍看之下會(huì)覺(jué)得它和merge好像是一樣的,其實(shí)還是有一些區(qū)別的。merge的作用是將多個(gè)不同的流合并成為一個(gè)流,而上圖中A1,A2,A3這三個(gè)流都是當(dāng)主流A返回?cái)?shù)據(jù)時(shí)新生成的,可以將他們想象為A的支流,如果你想在支流里撈魚(yú),就需要在每個(gè)支流里布網(wǎng),而flatMap相當(dāng)于提供了一張大網(wǎng),將所有A的支流里的魚(yú)都給撈上來(lái)。

所以在使用了flatMap后,就可以直接在一級(jí)訂閱中拿到需要的數(shù)據(jù)了:

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 函數(shù)式編程基礎(chǔ)知識(shí)回顧

如果本節(jié)的基本知識(shí)你尚不熟悉,可以通過(guò)javascript基礎(chǔ)修煉(8)——指向FP世界的箭頭函數(shù)這篇文章來(lái)簡(jiǎn)單回顧一下函數(shù)式編程的基本知識(shí),然后再繼續(xù)后續(xù)的部分。

/*map運(yùn)算符的作用
*對(duì)所有容器類(lèi)而言,它相當(dāng)于打開(kāi)容器,進(jìn)行操作,然后把容器再蓋上。
*Container在這里只是一個(gè)抽象定義,為了看清楚它對(duì)于容器中包含的值意味著什么。
*你會(huì)發(fā)現(xiàn)它其實(shí)就是Observable的抽象原型。
*/
Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}

//基本的科里化函數(shù)
var curry = function(fn){
  args = [].slice.call(arguments, 1);
  return function(){
     [].push.apply(args, arguments);
     return fn.apply(this, args);
  }
}

//map pointfree風(fēng)格的map運(yùn)算符
var map = curry(function(f, any_functor_at_all) {
  return any_functor_at_all.map(f);
});

/*compose函數(shù)組合方法
*運(yùn)行后返回一個(gè)新函數(shù),這個(gè)函數(shù)接受一個(gè)參數(shù)。
*函數(shù)科里化的基本應(yīng)用,也是函數(shù)式編程中運(yùn)算管道構(gòu)建的基本方法。
*/
var compose = function (f, g) {
    return function (x) {
        return f(g(x));
    }
};
/*IO容器
*一個(gè)簡(jiǎn)單的Container實(shí)現(xiàn),用來(lái)做流程管理
*這里需要注意,IO實(shí)現(xiàn)的作用是函數(shù)的緩存,且總是返回新的IO實(shí)例
*可以看做一個(gè)簡(jiǎn)化的Promise,重點(diǎn)是直觀感受一下它作為函數(shù)的
*容器是如何被使用的,對(duì)于理解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));
}

如果上面的基本知識(shí)沒(méi)有問(wèn)題,那么就繼續(xù)。

3.2 從一個(gè)容器的例子開(kāi)始

現(xiàn)在來(lái)實(shí)現(xiàn)這樣一個(gè)功能,讀入一個(gè)文件的內(nèi)容,將其中的a字符全部換成b字符,接著存入另一個(gè)文件,完成后在控制臺(tái)輸出一個(gè)消息,為了更明顯地看到數(shù)據(jù)容器的作用,我們使用同步方法并將其包裹在IO容器中,然后利用函數(shù)式編程:

var fs = require("fs");

//讀取文件
var readFile = (filename)=>IO.of(fs.readFileSync(filename,"utf-8"));

//轉(zhuǎn)換字符
var transContent = (content)=>IO.of((content)=>content.replace("a","b"));

//寫(xiě)入字符串
var writeFile = (content)=>IO.of(fs.writeFileSync("dest.txt",content));

當(dāng)具體的函數(shù)被IO容器包裹起來(lái)而實(shí)現(xiàn)延遲執(zhí)行的效果時(shí),就無(wú)法按原來(lái)的方式使用compose( )運(yùn)算符直接對(duì)功能進(jìn)行組合,因?yàn)?b>readFile函數(shù)運(yùn)行時(shí)的輸出結(jié)果(一個(gè)io容器實(shí)例)和transContent函數(shù)需要的參數(shù)類(lèi)型(字符串)不再匹配,在不修改原有函數(shù)定義的前提下,函數(shù)式編程中采用的做法是使用map操作符來(lái)預(yù)置一個(gè)參數(shù):

/*
*map(transContent)是一個(gè)高階函數(shù),它的返回函數(shù)就可以接收一個(gè)容器實(shí)例,
*并對(duì)容器中的內(nèi)容執(zhí)行map操作。
*/
var taskStep12 = compose(map(transContent), readFile);

這里比較晦澀,涉及到很多功能性函數(shù)的嵌套,建議手動(dòng)推導(dǎo)一下taskStep12這個(gè)變量的值,它的結(jié)構(gòu)是這樣一種形式:

io{
    __value:io{
        __value:someComposedFnExpression
    }
}

如果試圖一次性將所有的步驟組合在一起,就需要采用下面的形式:

var task = compose(map(map(writeFile)),map(transContent),readFile);
//組合后的task形式就是
//io{io{io{__value:someComposedFnExpression}}}

問(wèn)題已經(jīng)浮出水面了,每多加一個(gè)針對(duì)容器操作的步驟,書(shū)寫(xiě)時(shí)就需要多包裹一層map,而運(yùn)行時(shí)就需要多進(jìn)入一層才能觸及組合好的可以實(shí)現(xiàn)真正功能的函數(shù)表達(dá)式,真的是很麻煩。

 提示一

現(xiàn)在來(lái)回想一下原示例中的Observable對(duì)象,將其看做是一個(gè)容器(含有map類(lèi)方法),那么如果map方法調(diào)用時(shí)傳入的參數(shù)是一個(gè)運(yùn)行時(shí)會(huì)生成新的Observable對(duì)象的方法時(shí),就會(huì)產(chǎn)生Observable嵌套,得到observable{observable{.....}}這樣的結(jié)構(gòu),那么在最終的數(shù)據(jù)消費(fèi)者通過(guò)subscribe方法訂閱數(shù)據(jù)時(shí),就不得不用很多個(gè)subscribe才能拿到實(shí)際需要的數(shù)據(jù)。

提示二:

沒(méi)有相關(guān)經(jīng)驗(yàn)的讀者在使用pointfree風(fēng)格的map操作符時(shí)可能會(huì)感到非常不適應(yīng),如果你覺(jué)得它很難理解,也可以嘗試直接使用IO.prototype.map這種鏈?zhǔn)秸{(diào)用風(fēng)格的寫(xiě)法將上例中的三個(gè)步驟組合在一起來(lái)查看最后的結(jié)果,畢竟在Rxjs中常使用的也就是Observable這一個(gè)容器類(lèi)。

3.3 Monad登場(chǎng)

當(dāng)我們看到問(wèn)題所在后就不難發(fā)現(xiàn),其實(shí)這個(gè)問(wèn)題的解決方法并不復(fù)雜,我們要做的不過(guò)就是在必要的時(shí)候合并內(nèi)容的容器,為此來(lái)定義兩個(gè)合并運(yùn)算的方法:

//鏈?zhǔn)秸{(diào)用風(fēng)格
IO.prototype.join =  function(){
    return this.isNothing() ? IO.of(null):this.__value;
}
//pointfree風(fēng)格運(yùn)算符
var join = (m)=>m.join();

這里引入一個(gè)新的概念Monad,它的定義是可以被展平的容器,也就是說(shuō)擁有joinof方法并遵循一定規(guī)則的容器,都是Monad,在這種設(shè)定下,3.1中的示例就可以被改寫(xiě)為下面的形式:

var task = compose(join,map(writeFile),join,map(transContent),readFile);

不難發(fā)現(xiàn)mapjoin總是需要成對(duì)出現(xiàn)的,那么再利用函數(shù)科里化的技巧將mapjoin連起來(lái):

var chain = curry(function(f,m){
    return m.map(f).join();
})

那么組合后的函數(shù)就變成了下面的形式:

var task = compose(chain(writeFile),chain(transContent),readFile);

這里的chain,就是FlatMap

3.4 對(duì)比總結(jié)

最后將上面幾種形式放在一起再來(lái)回顧一下:

//原有形式
var task = compose(map(map(writeFile)),map(transContent),readFile);

//map-join形式
var task = compose(join,map(writeFile),join,map(transContent),readFile);

//chain形式(flatMap)
var task = compose(chain(writeFile),chain(transContent),readFile);

如果理解了這幾種形式,就不難理解flatMap的拉平效應(yīng)了,所謂flatMap,說(shuō)白了其實(shí)就是將容器展開(kāi)的一種操作。

3.5 一點(diǎn)疑問(wèn)

flatMap所解決問(wèn)題,是在函數(shù)式編程引入了Functor的概念將邏輯函數(shù)包裹在容器中后才產(chǎn)生的,那么這種容器概念的引入對(duì)函數(shù)式編程到底有什么意義,筆者尚未搞清楚,相關(guān)內(nèi)容留作以后補(bǔ)充。

四. 資料參考

《javascript函數(shù)式編程指南》https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/105390.html

相關(guān)文章

  • 響應(yīng)編程思維藝術(shù)】 (4)從打飛機(jī)游戲理解并發(fā)與流融合

    摘要:本文是響應(yīng)式編程第三章構(gòu)建并發(fā)程序這篇文章的學(xué)習(xí)筆記。筆者在自己的實(shí)現(xiàn)中又加入了右鍵切換飛船類(lèi)型的功能,必須得說(shuō)開(kāi)發(fā)游戲的確比寫(xiě)業(yè)務(wù)邏輯要有意思。由于沒(méi)有精確計(jì)算雪碧圖的坐標(biāo),所以在碰撞檢測(cè)時(shí)會(huì)有一些偏差。 本文是Rxjs 響應(yīng)式編程-第三章: 構(gòu)建并發(fā)程序這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大...

    mudiyouyou 評(píng)論0 收藏0
  • 響應(yīng)編程思維藝術(shù)】 (5)Angular中Rxjs應(yīng)用示例

    摘要:本文是響應(yīng)式編程第四章構(gòu)建完整的應(yīng)用程序這篇文章的學(xué)習(xí)筆記。涉及的運(yùn)算符每隔指定時(shí)間將流中的數(shù)據(jù)以數(shù)組形式推送出去。中提供了一種叫做異步管道的模板語(yǔ)法,可以直接在的微語(yǔ)法中使用可觀測(cè)對(duì)象示例五一點(diǎn)建議一定要好好讀官方文檔。 本文是【Rxjs 響應(yīng)式編程-第四章 構(gòu)建完整的Web應(yīng)用程序】這篇文章的學(xué)習(xí)筆記。示例代碼托管在:http://www.github.com/dashnoword...

    shenhualong 評(píng)論0 收藏0
  • 翻譯連載 | 附錄 B: 謙虛 Monad-《JavaScript輕量級(jí)函數(shù)編程》 |《你不知道

    摘要:就像我寫(xiě)書(shū)的過(guò)程一樣,每個(gè)開(kāi)發(fā)者在學(xué)習(xí)函數(shù)式編程的旅程中都會(huì)經(jīng)歷這個(gè)部分。類(lèi)型在函數(shù)式編程中有一個(gè)巨大的興趣領(lǐng)域類(lèi)型論,本書(shū)基本上完全遠(yuǎn)離了該領(lǐng)域。在函數(shù)式編程中,像這樣涵蓋是很普遍的。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML...

    gaomysion 評(píng)論0 收藏0
  • 編程 —— 函數(shù)編程入門(mén)

    摘要:在函數(shù)式編程中數(shù)據(jù)在由純函數(shù)組成的管道中傳遞。函數(shù)式編程中函子是實(shí)現(xiàn)了函數(shù)的容器下文中將函子視為范疇,模型可表示如下但是在函數(shù)式編程中要避免使用這種面向?qū)ο蟮木幊谭绞饺《畬?duì)外暴露了一個(gè)的接口也稱(chēng)為。 showImg(https://segmentfault.com/img/remote/1460000018101204); 該系列會(huì)有 3 篇文章,分別介紹什么是函數(shù)式編程、剖析函數(shù)...

    flyer_dev 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<