乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      函數(shù)式編程

       菌心說 2022-01-12

      文章內(nèi)容輸出來源:拉勾教育 大前端高薪訓(xùn)練營

      前言

      我在另一篇文章 函數(shù)式編程 – 純函數(shù)、柯里化函數(shù) 中寫到,副作用會(huì)讓一些函數(shù)變得不純,那么,我們?nèi)绾伟迅弊饔每刂圃诳煽氐姆秶鷥?nèi)呢,這就涉及到了函子的概念。

      函子(Functor)

      1. 什么是函子

      在開始學(xué)習(xí)之前,我們先來了解什么是函子?

      • 函子是一個(gè)容器,包含值和值的變形關(guān)系(即函數(shù))。

      • 函子是一個(gè)特殊的容器,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn),該對(duì)象具有 map 方法,map 方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理(變形關(guān)系)

        代碼如下(示例):

        // 一個(gè)容器,包裹一個(gè)值
        class Container {
            constructor (value) {	        
                this._value = value // 使用_表示變量私有化
            }
            // map方法, 傳入變形關(guān)系(函數(shù)),將容器里面的每一個(gè)值,映射到另一個(gè)容器
            map (fn) {
                return Container.of(fn(this._value))
            }
        }
        
        // 創(chuàng)建函子對(duì)象
        let r = new Container(5)
        	   .map(x => x + 1) // 返回新的函子對(duì)象, 在新的函子對(duì)象中保存值
        	   .map(x => x * x )
        console.log(r);
        

        上面的代碼中,Container 是一個(gè)函子,它的map方法接受函數(shù)f作為參數(shù),然后返回一個(gè)新的函子,里面包含的值是被 fn 處理過的(fn(this._value))。
        上面生成新的函子對(duì)象的時(shí)候,用了 new 命令。new 命令是面向?qū)ο缶幊痰臉?biāo)志,不符合函數(shù)式編程的思想。

      • 函數(shù)式編程一般約定,函子有一個(gè)of方法,用來生成新的容器。

        那么,我們接下來就用 of 方法替換掉 new 進(jìn)行改造。

        代碼如下(示例):

        class Container {
            // of 使用static,將其設(shè)置為靜態(tài)方法,可以使用 '類.類方法' 的方式調(diào)用
            static of (value) {
                return new Container(value)
            }
            ...... // 下面代碼和上面的一樣,就不在此贅述了
        }
        // 鏈?zhǔn)骄幊?/span>
        let r = Container.of(5).map(x => x + 2).map(x => x * x)
        console.log(r);		
        
      • 總結(jié)

        1、函數(shù)式編程的運(yùn)算不直接操作值,而是由函子完成
        2、函子就是一個(gè)實(shí)現(xiàn)了 map 契約的對(duì)象
        3、我們可以把函子想象成一個(gè)盒子,這個(gè)盒子里封裝了一個(gè)值
        4、想要處理盒子中的值,我們需要給盒子的 map 方法傳遞一個(gè)處理值的函數(shù)(純函數(shù)),由這個(gè)函數(shù)來對(duì)值進(jìn)行處理
        5、最終 map 方法返回一個(gè)包含新值的盒子(函子)

      2. MayBe 函子

      空值問題

      • 函子接受各種函數(shù),處理容器內(nèi)部的值。但是,當(dāng)容器內(nèi)部的值是一個(gè)空值(比如null),而外部函數(shù)未必有處理空值的機(jī)制,如果傳入空值,很可能就會(huì)出錯(cuò)。

        代碼如下(示例):

        // 值如果不小心傳入了空值(副作用) 
        Container.of(null) .map(x => x.toUpperCase()) 
        // TypeError: Cannot read property 'toUpperCase' of null 12
        

      解決方案

      • MayBe 函子的作用就是可以對(duì)外部的空值情況做處理(控制副作用在允許的范圍),準(zhǔn)確的說是,它的 map 方法里面設(shè)置了空值檢查。

        代碼如下(示例):

        class Maybe {
            map (fn) {
                return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this._value))
            }
        
            isNothing () {
                return this._value == null || this._value == undefined
            }
        }
        
        // 測試
        let r = Maybe.of('Hello World').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '))
        console.log(r);
        

        然而,在 MayBe 函子中,我們很難確認(rèn)是哪一步產(chǎn)生的空值問題,要解決這個(gè)問題,我們就要借助下面的 Either 函子 ,去處理異常情況。

      3. Either 函子

      在普通的面向?qū)ο缶幊讨?,我們通常使用條件運(yùn)算語句 if…else… 進(jìn)行異常等方面的判斷。而在函數(shù)式編程中,我們是用 Either 函子 進(jìn)行表達(dá)。Either,英文意思,兩者中的任何一個(gè)。

      • Either 函子內(nèi)部有兩個(gè)值:左值(Left)和右值(Right)。右值是正常情況下使用的值,左值是右值不存在時(shí)使用的默認(rèn)值。

        代碼如下(示例):

        // 記錄錯(cuò)誤信息, 右值不存在時(shí)使用的默認(rèn)值
        class Left {
            static of (value) {
                return new Left(value)
            }
        
            constructor (value) {
                this._value = value
            }
        
            map (fn) {
                return this
            }
        }
        
        // 正常情況下使用的值
        class Right {
            static of (value) {
                return new Right(value)
            }
        
            constructor (value) {
                this._value = value
            }
        
            map (fn) {
                return Right.of(fn(this._value))
            }
        }
        
        // Either 用來處理異常
        function parseJSON (str) {
            try {
                return Right.of(JSON.parse(str))
            } catch (e) {
                return Left.of({error: e.message })
            }
        }
        
        // let r = parseJSON('{ name: zs }')
        // console.log(r) // 執(zhí)行 Left
        
        let r = parseJSON('{ 'name': 'zs' }').map(x => x.name.toUpperCase())
        console.log(r) // 執(zhí)行 Right
        

      4. IO 函子

      在程序運(yùn)行中,往往會(huì)有很多的函數(shù)依賴于外部環(huán)境,從而會(huì)帶來相應(yīng)的副作用,這也就是我們前面所說的不純函數(shù),在這里,我們就不多加贅述了。那么,如何可以把不純的函數(shù),讓它 “純”起來呢?為了解決這個(gè)問題,我們需要一個(gè)新的 Functor,即 IO 函子。

      特性

      • IO 函子與其他函子的不同在于,IO 函子中的 _value 是一個(gè)函數(shù),把函數(shù)作為值來處理。

      • IO 函子可以把不純的動(dòng)作存儲(chǔ)到 _value(函數(shù)) 中,延遲執(zhí)行這個(gè)不純的操作(惰性執(zhí)行)。可以認(rèn)為,IO 包含的是被包裹的操作的返回值。

      • IO 函子把不純的操作交給調(diào)用者來處理。

        代碼如下(示例):

        const { values } = require('lodash')
        const fp = require('lodash/fp')
        
        class IO {
            static of (value) { // 
                return new IO(function () {
                    return value
                })
            }
        
            constructor (fn) { // value 存儲(chǔ)函數(shù)
                this._value = fn
            }
        
            map (fn) {
            	// 將傳入的 fn 進(jìn)行包裹,利用fp.flowRight() 使之柯里化
                return new IO(fp.flowRight(fn, this._value))
            }
        }
        
        // 調(diào)用,process:node中的進(jìn)程模塊
        let r = IO.of(process).map(p => p.execPath)
        // console.log(r) // IO { _value: [Function] }
        console.log(r._value()); // 當(dāng)前node進(jìn)程的執(zhí)行路徑
        

      5. Folktale

      • folktale 一個(gè)標(biāo)準(zhǔn)的函數(shù)式編程庫,和 lodash、ramda 不同的是,他沒有提供很多功能函數(shù)。

      • 只提供了一些函數(shù)式處理的操作,例如:compose、curry 等,一些函子 Task、Either、 MayBe 等。

        代碼如下(示例):

        // Folktale 函數(shù)式編程庫
        const { toUpper, first } = require('lodash/fp')
        const { compose, curry } = require('folktale/core/lambda')
        
        // 第一個(gè)參數(shù)是傳入函數(shù)的參數(shù)個(gè)數(shù)
        let f = curry(2, (x, y) => x + y)
        console.log(f(1, 2));
        console.log(f(1)(2));
        
        let f = compose(toUpper, first)
        console.log(f(['one', 'two']));
        
      • Task 異步執(zhí)行
        Task 函子通過類似 Promise 的 resolve 的風(fēng)格來聲明一個(gè)異步流程,在下面的代碼中聲明的 readFile 函數(shù)中返回的 Task 函子 并沒有真正發(fā)起請(qǐng)求,它只聲明了一個(gè)請(qǐng)求動(dòng)作,這個(gè)動(dòng)作并沒有被執(zhí)行。

        代碼如下(示例):

        const fs = require('fs')
        const { task } = require('folktale/concurrency/task')
        const { split, find } = require('lodash/fp') 
        
        function readFile (filename) {
        	// 通過類似 Promise 的 resolve 的風(fēng)格來聲明一個(gè)異步流程,返回一個(gè)Task 函子
            return task(resolver => {
            	// fs 的readFile() 執(zhí)行的是異步操作
                fs.readFile(filename, 'utf-8', (err, data) => {
                	// 類似Promise中的resolve 和 reject
                	// reject用來報(bào)錯(cuò)誤信息,resolve用來獲取執(zhí)行成功的數(shù)據(jù)。
                    if (err) resolver.reject(err)
        	        resolver.resolve(data)
                })
            })
        }
        
        let version = readFile('../package.json') // 只聲明讀取文件的動(dòng)作,該動(dòng)作并未執(zhí)行
        			 .map(split('\n'))   // 通過 map 方法,添加不同的數(shù)據(jù)操作流程。
                     .map(find(x => x.includes('version'))) // includes() 方法用于判斷字符串是否包含指定的子字符串。
                     .run()           // 調(diào)用 run() 觸發(fā)上面的動(dòng)作,進(jìn)行
        		     .listen({
        		         onRejected: err => { // 執(zhí)行失敗
        		             console.log(err);
        		         },
        		         onResolved: value => { // 執(zhí)行成功
        		             console.log(value);
        		         }
        		     })
        console.log(version); // 'version': '1.0.0'
        

      在上面的代碼中,Task 的異步流直到 run 之前都僅僅是「動(dòng)作」,沒有「執(zhí)行」。task 函子中提供了 run() 方法,用來觸發(fā)動(dòng)作的執(zhí)行。也就是說,執(zhí)行 run 方法之后,才會(huì)觸發(fā)上面的文件讀取,以及對(duì)文件內(nèi)容的一系列處理等操作。Task 函子中,還提供了 listen() 方法,用來監(jiān)聽事件的執(zhí)行狀態(tài)。onRejected 表示 動(dòng)作執(zhí)行失敗后,要執(zhí)行的函數(shù),onResolved 表示 動(dòng)作執(zhí)行成功后,要執(zhí)行的函數(shù)。

      6. Pointed 函子

      • Pointed 函子是實(shí)現(xiàn)了 of 靜態(tài)方法的函子;

      • of 方法是為了避免使用 new 來創(chuàng)建對(duì)象,更深層的含義是 of 方法用來把值放到上下文
        Context(把值放到容器中,使用 map 來處理值)

        代碼如下(示例):

        class Container { 
        	static of (value) { 
        		return new Container(value) 
        	}
        	......
        }
        Contanier.of(2) .map(x => x + 5)
        

      7. Monad(單子)

      • 在使用 IO 函子的時(shí)候,如果我們寫出如下代碼:

        代碼如下(示例):

        const fs = require('fs') 
        const fp = require('lodash/fp') 
        let readFile = function (filename) { 
        	return new IO(function() { // 返回一個(gè)文件類型的實(shí)例
        		return fs.readFileSync(filename, 'utf-8') 
        	}) 
        }
        let print = function(x) { 
        	return new IO(function() { 
        		console.log(x)  // 將文件內(nèi)容輸出
        		return x 
        	})
        } 
        
        // IO(IO(x)) 
        // 調(diào)用 _value() 時(shí),執(zhí)行的是print 中的function
        let cat = fp.flowRight(print, readFile) 
        
        // 調(diào)用 
        let r = cat('package.json')._value()._value() 
        console.log(r)
        
        

      特性

      • Monad 函子是可以變扁的 Pointed 函子,IO(IO(x))

      • Monad 內(nèi)部封裝的值是一個(gè)函數(shù)(這個(gè)函數(shù)返回函子)

      • 一個(gè)函子如果具有 join 和 of 兩個(gè)方法并遵守一些定律就是一個(gè) Monad

        代碼如下(示例):

        // IO Monad
        const fs = require('fs')
        const fp = require('lodash/fp')
        
        class IO {
            static of (value) {
                return new IO(function () {
                    return value
                })
            }
        
            constructor (fn) {
                this._value = fn
            }
        
            map (fn) {
                return new IO(fp.flowRight(fn, this._value))
            }
        	
        	// 通過 join 方法避免函子嵌套
            join () { 
                return this._value()
            }
        
            // 同時(shí)調(diào)用map 和 join
            flatMap (fn) {
                // this.map(fn) 調(diào)用完后,返回函子
                return this.map(fn).join()
            }
        }
        
        // 讀取文件的內(nèi)容,并且把他們打印出來
        let readFile = function (filename) {
            return new IO(function () {
                return fs.readFileSync(filename, 'utf-8') // 同步讀取文件
            })
        }
        
        let print = function (x) {
            return new IO(function () {
                console.log(x)
                return x
            })
        }
        
        let r = readFile('../package.json') // 返回函子時(shí),調(diào)用faltMap; 返回值時(shí),調(diào)用map
                // .map(x => x.toUpperCase())
                .map(fp.toUpper)
                .flatMap(print)    // 返回 IO { _value: [Function] } -- 函子
                .join()            // 返回 map 后的文件內(nèi)容
        console.log(r);
        

      作用

      • Monad 函子 主要用來解決函子嵌套的問題,通過 join 方法避免函子嵌套。

      何時(shí)使用

      • 當(dāng)一個(gè)函數(shù)返回一個(gè)函子的時(shí)候,需要使用 Monad。

      總結(jié)

      • 簡單說,Monad就是一種設(shè)計(jì)模式,表示將一個(gè)運(yùn)算過程,通過函數(shù)拆解成互相連接的多個(gè)步驟。你只要提供下一步運(yùn)算所需的函數(shù),整個(gè)運(yùn)算就會(huì)自動(dòng)進(jìn)行下去。也就是說,Monad 是將一個(gè)會(huì)返回包裹值的函數(shù)應(yīng)用到一個(gè)被包裹的值上。

      參考
      函數(shù)式編程入門教程
      異步流程與 Task 函子
      JavaScript函數(shù)式編程 IO涵子,錯(cuò)誤處理涵子

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多