函數(shù)式編程

拉勾大前端的筆記捅位,僅作為學(xué)習(xí)記錄

課程介紹

  • 為什么學(xué)習(xí)函數(shù)式編程怒见,以及什么是函數(shù)編程
  • 函數(shù)式編程的特性(純函數(shù),柯里化禽车,函數(shù)組合)
  • 函數(shù)式編程的應(yīng)用場(chǎng)景
  • 函數(shù)式編程庫 Lodash

為什么學(xué)些函數(shù)式編程

隨著React 的流行備受關(guān)注寇漫,Vue3.0也開始擁抱函數(shù)式,函數(shù)式可以拋棄this殉摔,打包過程中更好的利用tree shaking 過濾無用代碼州胳,方便測(cè)試,方便并行處理逸月,有很多庫可以幫助我們進(jìn)行函數(shù)開發(fā) Lodash,underscorce,ramda

什么是函數(shù)編程

函數(shù)式編程是編程范式之一栓撞,常說的還有面向?qū)ο蠛兔嫦蜻^程
面向?qū)ο?/strong>:把現(xiàn)實(shí)世界中的事物抽象成程序世界中的類和對(duì)象,通過封裝碗硬、繼承瓤湘、多態(tài)演示事物之間的聯(lián)系
函數(shù)式編程: 把現(xiàn)實(shí)世界中事物和事物之間的聯(lián)系抽象成程序世界,是對(duì)運(yùn)算過程的抽象恩尾,像純函數(shù)(相同的輸入得到相同的輸出) eg. y=sin(x-0)

函數(shù)式編程--前置知識(shí)

  • 函數(shù)是一等公民
  • 高階函數(shù)
  • 閉包

函數(shù)是一等公民

  • 函數(shù)可以存儲(chǔ)在變量中
  • 函數(shù)可以作為參數(shù)
  • 函數(shù)可以作為返回值
    在JavaScript中函數(shù)式一個(gè)普通的對(duì)象 (可以通過 new Function)弛说,我們可以把函數(shù)存儲(chǔ)在變量/數(shù)組中,它還可以作為另一個(gè)函數(shù)的參數(shù)和返回值翰意,甚至我們可以在程序運(yùn)行的時(shí)候通過new Function(‘a(chǎn)lert(1)’)來構(gòu)造一個(gè)新的函數(shù)
把函數(shù)賦值給變量
let fn = function(){
  console.log(`hello`)
}
fn()

高階函數(shù)

  • 高階函數(shù)(Higher-order function)
    • 可以把函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)
    • 可以把函數(shù)作為另一個(gè)函數(shù)的返回結(jié)果

函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)

// 模擬forEach
function forEach(arr,fn){
  for( let i = 0; i < arr.length; i++ ){
    fn(arr[i])
  }
}
//模擬filter
function filter(arr,fn){
  const res = []
  for( let i = 0; i < arr.length; i++ ){
    if(fn(arr[i])){
      res.push(arr[i])
    }
  }
  return res
}

函數(shù)作為返回值

function makeFn(){
  let msg = `hello function`
  return function(){
    console.log(msg)
  }
}
// 模擬once 使用場(chǎng)景:支付場(chǎng)景
function once(fn){
  const done = false
  return function(){
    if(!done){
      done = true
      return fn.apply(this, arguments)   // 用戶調(diào)用的時(shí)候可能會(huì)傳遞參數(shù)剃浇,所以把調(diào)用當(dāng)前函數(shù)的參數(shù)傳遞給fn
    }
  }
}
let pay = once(function(money){
  console.log(`支付了 ${money} RMB`)
})

使用高階函數(shù)的意義

  • 抽象可以幫我們屏蔽細(xì)節(jié)巾兆,只需要關(guān)注于我們的目標(biāo)
  • 高階函數(shù)是用來抽象通用的問題

常見的高階函數(shù)

// 模擬map
const map = (arr,fn)=>{
  const results = []
  for(const value of arr ){
    results.push(fn(value))
  }
  return results 
}
// 模擬every
const every = (array, fn) => {
  let result = true
  for(let value of array){
    result = fn(value)
    if(!result){
      break
    }
  }
return result
}
// 模擬some
const some = (array, fn) => {
  let result = false
  for(let value of array){
    result = fn(value)
    if(!result){
      break
    }
  }
return result
}

閉包

閉包的概念

閉包(Closure): 函數(shù)和其周圍的狀態(tài)(詞法環(huán)境)的引用捆綁在一起行程閉包

  • 可以在另一個(gè)作用于中調(diào)用一個(gè)函數(shù)的內(nèi)部函數(shù)并訪問到改函數(shù)的作用域中的成員
function makeFn (){
  let msg =  `Hello`
  return function(){
    console.log(msg)
  }
}
const fn = makeFn()  // 因?yàn)橥獠繉?duì)內(nèi)部的函數(shù)有引用,所以內(nèi)部的msg變量不能被釋放掉
fn()
- 閉包的本質(zhì):函數(shù)在執(zhí)行的時(shí)候會(huì)被放到一個(gè)執(zhí)行棧上,當(dāng)函數(shù)執(zhí)行完畢后虎囚,會(huì)被從執(zhí)行棧中移出角塑,**但是堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫?*,因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)成員
閉包 的案例
// 求平方
function makePower (power){
  return function (number){
    Math.pow(number,power)
  }
}
const power2 = makePower(3) // 求3的平方
// 求員工的工資
function makeSalary (base){
  return function (performance){
    return base + performance
  }
}

純函數(shù)

純函數(shù)的概念

純函數(shù) 相同的輸入永遠(yuǎn)會(huì)得到相同的輸出淘讥,而且沒有任何可觀察的副作用

  • 純函數(shù)就類似于數(shù)學(xué)中的函數(shù)圃伶,用來描述輸入和輸出之間的關(guān)系,y=f(x)
  • lodash 是一個(gè)純函數(shù)的功能庫,提供了對(duì)數(shù)組蒲列、數(shù)字窒朋、對(duì)象、字符串蝗岖、函數(shù)等操作的一些方法
  • 數(shù)組中的slice 和 splice 分別是:純函數(shù)和不純函數(shù)
    • slice 返回?cái)?shù)組中的指定部分侥猩,不會(huì)改變?cè)瓟?shù)組
    • splice 對(duì)數(shù)組進(jìn)行操作返回?cái)?shù)組,會(huì)改變?cè)瓟?shù)組
let array = [1,2,3,4,5]
// 純函數(shù)
console.log(array.slice(0,3)) // [1,2,3]
console.log(array.slice(0,3)) // [1,2,3]
// 不純函數(shù)
console.log(array.splice(0,3)) // [1,2,3]
console.log(array.splice(0,3)) // [4,5]
  • 函數(shù)式編程不會(huì)保留計(jì)算中間的結(jié)果抵赢,所以變量是不可變的(無狀態(tài)的)
  • 我可以把一個(gè)函數(shù)的執(zhí)行結(jié)果交給另一個(gè)函數(shù)去處理

Lodash中提供的純函數(shù)

const array = ['jack','lucy','mack','nike']
console.log(_.first(array)) // jack
console.log(_.last(array)) // nike
console.log(_.toUpper(_.first(array))) JACK

純函數(shù)的好處

  • 可緩存
    因?yàn)橄嗤妮斎胧冀K會(huì)有相同的結(jié)果欺劳,所以可以把純函數(shù)的結(jié)果緩存起來
    使用緩存的原因:如果有個(gè)函數(shù)調(diào)用起來特別耗時(shí),并且需要多次調(diào)用铅鲤,可以在第一次調(diào)用的時(shí)候把結(jié)果緩存起來划提,第二次調(diào)用直接返回緩存的結(jié)果
// lodash中的記憶函數(shù)
const _ = require('lodash')
function getArea(r){
  console.log(r)
  return Math.PI *r *r
}
let getAreaWithMemory =  _.memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
// 4
// 圓的面積
// 圓的面積
// 圓的面積

模擬memoize

function memoize(f){
  const cache = {}
  return function(){
   let key = JSON.stringfy(arguments)
   cache[key] = cache[key] || f.apply(f,arguments) 
    return cache[key]
  }
}
  • 可測(cè)試
    因?yàn)榧兒瘮?shù)始終有輸入輸出,讓測(cè)試更方便
  • 并行處理
    • 在多線程環(huán)境下并行操作共享的內(nèi)存數(shù)據(jù)可能會(huì)發(fā)生意外情況
    • 純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù)邢享,所以在并行環(huán)境下可以任意運(yùn)行純函數(shù)

純函數(shù)的副作用

  • 純函數(shù):對(duì)于相同的輸入永遠(yuǎn)會(huì)得到相同的輸出鹏往,而且沒有任何可觀察的副作用
// 不純的
let mini = 18
function checkAge(age){
  return age >= mini
}
// 純的(有硬編碼,后續(xù)通過柯里化解決)
function checkAge (age){
  let mini = 18
  return age >= mini  
}

副作用讓一個(gè)函數(shù)變得不純(如上例)骇塘,純函數(shù)是根據(jù)相同的輸入返回相同的輸出伊履,如果函數(shù)依賴于外部的狀態(tài)就無法保成輸出相同,就會(huì)帶來副作用
副作用的來源:

  • 配置文件
  • 數(shù)據(jù)庫
  • 獲取用戶的輸入
  • ...
    所有的外部交互都有可能帶來副作用款违,副作用也使得方法通用性下降不適合拓展和可重用性湾碎,同時(shí)副作用會(huì)給程序帶來安全應(yīng)還給程序帶來不確定性,但是副作用不可能完全禁止奠货,盡可能控制他們?cè)诳煽胤秶鷥?nèi)發(fā)生

柯里化

柯里化概念

  • 當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候先傳遞一部分參數(shù)調(diào)用它(這部分參數(shù)以后永遠(yuǎn)不變)
  • 然后返回一個(gè)新的函數(shù)接受剩余的參數(shù)介褥,返回結(jié)果
    使用柯里化解決上一個(gè)案例中硬編碼的問題
// 存在硬編碼的函數(shù)
function checkAge(age){
  let min = 18
  return age >= mini
}
// 普通的純函數(shù)
function checkAge( min, age){
  return age >= min
}
// 函數(shù)的柯里化
function checkAge(min){
  return function (age){
    return age >= mini
  }
}
// ES寫法
let checkAge = min => (age=> age >= min)

let checkAge18 = checkAge(18)
checkAge18(20) // true

Lodash中的柯里化函數(shù)

_.curry(func)
  • 功能:創(chuàng)建一個(gè)函數(shù),該函數(shù)接收一個(gè)或多個(gè)func的參數(shù)递惋,如果func所需要的參數(shù)都被提供則執(zhí)行func并返回執(zhí)行結(jié)果柔滔,否則繼續(xù)執(zhí)行返回該函數(shù)并等待接收剩余的參數(shù)
  • 參數(shù): 需要柯里化的函數(shù)
  • 返回值: 柯里化后的函數(shù)
// lodash 中 curry 的基本使用
// 柯里化可以把任意多元的函數(shù)轉(zhuǎn)化為一元的函數(shù)
const _ = require('lodash')
function getSum(a,b,c){
  return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(1,2,3)) // 6 因?yàn)閭鬟f了func所需的所有參數(shù),func會(huì)被執(zhí)行并返回執(zhí)行結(jié)果
console.log(curried(1)(2,3)) //當(dāng)傳遞了一個(gè)函數(shù)后萍虽,會(huì)返回一個(gè)新的函數(shù)并等待接收剩余參數(shù)
console.log(curried(1,2)(3)) 

柯里化的案例

// 匹配字符串中的所有空白字符
// 之后還需要提取字符串中的數(shù)字
// 面向過程的方式
' '.match(/\s+/g)
' '.match(/\d+/g)
// 純函數(shù)
function match (reg,str) {
  return str.match(reg)
}
// 柯里化處理
const _ = require('lodash')
const match = _.curry(function(reg,str){
  return str.match(reg)
})

const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
const filter = _.curry((func,array) => array.filter(func))

const findSpace = filter(haveSpace)
findSpace(['hello world','jkl'])

柯里化實(shí)現(xiàn)原理

function curry(func){
  return function curriedFn(...args){
    // 判斷實(shí)參和形參的個(gè)數(shù)
    if(args.length < func.length){
      return function (){
        return curriedFn(...args.concat(Array.form(arguments)))
      }
    }
    return func(...args)
  }
}

柯里化總結(jié)

  • 柯里化可以讓我們給一個(gè)函數(shù)傳遞較少的參數(shù)得到一個(gè)已經(jīng)記住了某些固定參數(shù)的新函數(shù)
  • 這是一種對(duì)函數(shù)參數(shù)的‘緩存’
  • 讓函數(shù)變得更靈活睛廊,讓函數(shù)的粒度更小
  • 可以把多元函數(shù)轉(zhuǎn)化為一元函數(shù),可以組合使用參數(shù)產(chǎn)生強(qiáng)大的功能

函數(shù)組合 Compose

純函數(shù)和柯里化很容易寫出洋蔥代碼h(g(f(x)))

  • 獲取數(shù)組的最后一個(gè)元素再轉(zhuǎn)換成大寫字母
_.toUpper(_.first(_.reverse(array)))

函數(shù)組合可以讓我們把細(xì)粒度的函數(shù)重新組合生成一個(gè)新的函數(shù)

管道

下面這張圖表示程序中使用函數(shù)處理數(shù)據(jù)的過程杉编,給fn函數(shù)輸入?yún)?shù)a超全,返回結(jié)果b,可以想到a數(shù)據(jù)通過管道得到了b數(shù)據(jù)

image.png

當(dāng)函數(shù)fn比較復(fù)雜的時(shí)候咆霜,我們可以把函數(shù)fn拆成多個(gè)小函數(shù),此時(shí)多了中間運(yùn)算過程產(chǎn)生的m和n

下面這張圖可以想象成把fn這個(gè)管道拆分成3個(gè)管道f1,f2,f3,數(shù)據(jù)a通過管道f3得到結(jié)果m,m再通過管道f2得到結(jié)果n,n通過管道f1得到最終的結(jié)果b


image.png
fn = compose(f1,f2,f3)
b = fn(a)

函數(shù)組合概念

函數(shù)組合(compose) 如果一個(gè)函數(shù)經(jīng)過多個(gè)函數(shù)處理才能得到最終值嘶朱,這個(gè)時(shí)候可以把中間過程的函數(shù)合并成一個(gè)函數(shù)

  • 函數(shù)就像是一個(gè)數(shù)據(jù)的管道蛾坯,函數(shù)組合就是把這些管道連接起來,讓數(shù)據(jù)穿過多個(gè)管道形成最終結(jié)果
  • 函數(shù)組合默認(rèn)是從右到左執(zhí)行
// 組合函數(shù)演示
// 獲取數(shù)組的最后一元素
function compose(f, g){
    return function(value){
        f(g(value))
    }
}

function reverse(array){
    return array.reverse()
}

function first(array){
    return array[0]
}

const last = compose(first, reverse)
console.log(last([1,2,3,4]))

Lodash中的組合函數(shù)

  • lodash中提供組合函數(shù)flow()或者flowRight(),他們可以組合多個(gè)函數(shù)
  • flow()是從左到右運(yùn)行
  • flowRight()是從右到左運(yùn)行疏遏,使用更多一些
// lodash 中的 _.flowRight()
const _ = require('lodash')
const reverse = array => array.reverse()
const first = array => array[0]
const toUpper = array => array.toUpperCase()

const fn = _.flowRight(toUpper, first, reverse)
console.log(fn(['one, 'two', 'three']))

flowRight的實(shí)現(xiàn)原理

function compose(...args){
    return function(value){
        return args.reverse().reduce(function(acc,fn){
            return fn(acc)
        }, value)
    }
}
// es6改寫
const compose = (...args) => value => args.reverse().reduce( (acc,fn )=> fn(acc), value)

函數(shù)組合

結(jié)合律
函數(shù)組合要滿足結(jié)合律

  • 我們既可以把g和h組合脉课,還可以把f和g組合,結(jié)果都是一樣的
// 結(jié)合律
const f = _.flowRight(_.toUpper, _.first, _.reverse)
const f = _.flowRight(_.flowRight(_.toUpper,_.first),_.reverse)
const f = _.flowRight(_.toUpper,_.flowRight(_.first, _.reverse))

調(diào)試

// NEVER SAY DIE --> never-say-die
// 思路:通過過濾空格财异,將字符串轉(zhuǎn)化為數(shù)組倘零,把數(shù)組的每一項(xiàng)變?yōu)樾懀ㄟ^字符串‘-’分割數(shù)組

const split = _.curry((sep,str) => _.split(str,sep))
const map = _.curry((fn,array)=> _.map(array,fn))
const join = _.curry((spe,array)=>_.join(array,spe))

// 調(diào)試的時(shí)候可以寫一個(gè)輔助函數(shù)戳寸,看上一個(gè)函數(shù)的執(zhí)行結(jié)果呈驶,并把執(zhí)行結(jié)果返回給下一個(gè)待執(zhí)行的函數(shù)
const log = v => {
    console.log(v)
    return v
}
// 改造log
const trace = _.curry((tag,v)=>{
  console.log(tag,v)
  return v
})

const fn = _.flowRight( join('-'), trace('在map之后打印的'),map(_.toLower),  log, split(' '))
console.log('NEVER SAY DIE')

Lodash中的FP模塊

  • lodash的fp模塊提供了實(shí)用的對(duì)函數(shù)式編程友好的方法
  • 提供了不可變 已經(jīng)被柯里化的(auto-curried)并且遵循函數(shù)有先(iteratee-first) 數(shù)據(jù)滯后(data-last)的方法
const _ = require('lodash')
// lodash 模塊中數(shù)據(jù)優(yōu)先函數(shù)置后
_.map(['a','b','c'],_.toUpper)
_.split('hello world', ' ')

const fp = require('lodash/fp')
// fp模塊中函數(shù)優(yōu)先,數(shù)據(jù)置后
fp.map(fp.toUpper,['a','b','c'])
fp.map(fp.toUpper)(['a','b','c'])
fp.split(' ', 'hello world')
fp.split(' ')('hello world')
// 通過fp改造之前的Never SAY DIE -> nerver-say-die 案例
const fp = require('lodash/fp')
const fn = fp.flowRight(fp.join('-'),fp.map(fp.toLower,fp.split(' ')))

lodash 中的map和fp中的map區(qū)別

  • lodash中的map后面的function接收三個(gè)參數(shù) value:any, index|key, array
  • fp中的map中function只接收一個(gè)參數(shù) value:any

Point Free

定義:我們可以把數(shù)據(jù)處理的過程定義成與數(shù)據(jù)無關(guān)的合成運(yùn)算疫鹊,不需要用到代表數(shù)據(jù)的那個(gè)參數(shù)袖瞻,只需要簡(jiǎn)單的把運(yùn)算步驟合成到一起,在使用這種模式之前我們需要定義一些輔助的基本運(yùn)算函數(shù)

  • 不需要指明處理的數(shù)據(jù)
  • 只需要合成運(yùn)算的過程
  • 需要定義一些輔助的基本運(yùn)算函數(shù)
// Hello  World -> hello_world
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/s+/g, '_'), fp.toLower)

案例

// world wild web ==> W. W. W.
const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.first), fp.map(fp.toUpper), fp.split(' '))
// 改造
const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first,fp.toUpper)), fp.split(' '))

函子Functor

為什么學(xué)習(xí)函子

到目前為止已經(jīng)學(xué)習(xí)了函數(shù)式編程的一些基礎(chǔ)订晌,但是我們還沒有演示在函數(shù)式編程中如何把副作用控制到可控范圍內(nèi)、異常處理蚌吸、異步操作等

什么是Functor

  • 容器:包含值和值得變形關(guān)系(這個(gè)變形關(guān)系就是函數(shù))
  • 函子:是一個(gè)特殊的容器锈拨,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn),該對(duì)象具有map方法羹唠,map方法可以運(yùn)行一個(gè)函數(shù)奕枢,對(duì)值進(jìn)行處理(變形關(guān)系)
// Functor 函子
class Container { // 創(chuàng)建函子類
  constructor (value) {
    this._value = value // 接收一個(gè)value,存儲(chǔ)在內(nèi)部
  }
  map(fn){ // 內(nèi)部有個(gè)map方法接收一個(gè)純函數(shù),處理完數(shù)據(jù)并返回一個(gè)函子對(duì)象
    return new Container(fn(this._value))
  }
}
// 調(diào)用
let r = new Container(5)
     .map(x => x + 1)
     .map(x => x * x)
console.log(r) // 36

// 對(duì)上面的class 類進(jìn)行改造佩微,封裝靜態(tài)方法of, new Container創(chuàng)建函子對(duì)象
class Container {
  static of (value){
    return new Container(value)
  }
  constructor (value) {
    this._value = value // 接收一個(gè)value,存儲(chǔ)在內(nèi)部屬性_value中
  }
  map(fn){ // 內(nèi)部有個(gè)map方法接收一個(gè)純函數(shù)缝彬,處理完數(shù)據(jù)并返回一個(gè)函子對(duì)象
    return Container.of(fn(this._value))
  }
}

函子總結(jié)

  • 函數(shù)式編程的運(yùn)算不直接操作值,而是由函子完成
  • 函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象
  • 我們可以把函子想象成一個(gè)盒子哺眯,這個(gè)盒子里封裝了一個(gè)值
  • 想要處理盒子中的值谷浅,我們需要給盒子的map方法傳遞一個(gè)處理值的函數(shù)(純函數(shù)),由這個(gè)函數(shù)對(duì)值進(jìn)行處理
  • 最終map方法返回一個(gè)包含新值得盒子(函子)
// 演示 null undefined 的問題
Container.of(null)
.map(x => x.toUpperCase())
// 傳遞null會(huì)報(bào)錯(cuò)奶卓,不符合純函數(shù)的特征(相同的輸入會(huì)有相同的輸出)

MayBe函子

  • 我們?cè)倬幊痰倪^程中可能會(huì)處理很多錯(cuò)誤一疯,需要對(duì)這些錯(cuò)誤做相應(yīng)的處理
  • MayBe 函子的作用就是可以對(duì)外部的空值情況做處理(控制副作用在允許的范圍)
class MayBe {

    static of(value){
        return new MayBe(value)
    }
    constructor(value){
        this._value = value
    }
    map(fn){
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(value))
    }
    isNothing(){
        return this._value === null || this._value  === undefined
    }
}
// MayBe 函子的問題
let r = MayBe.of('hello world')
  .map(x => x.toUpperCase())
  .map(null)
  .map(x => x.split(' '))
console.log(r)  // MayBe{_value: null }
// 不知道是什么地方出現(xiàn)了null

Either函子

  • Either兩者中的任何一個(gè),類似于 if...else 的處理
  • 異常會(huì)讓函數(shù)變得不存夺姑,Either函子可以用來做異常處理
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))
    }
}

function parseJSON(str){
    try{
        return Right(JSON.parse(str))
    }catch(e){
        return Left({error:e.message})
    }
}

const r = parseJSON('{name:zs}')
console.log(r) // 走Left

IO函子

  • IO函子中的_value是一個(gè)函數(shù)墩邀,這里是把函數(shù)作為值來處理
  • IO函子可以把不純的動(dòng)作存儲(chǔ)到_value中,延遲執(zhí)行這個(gè)不純的操作(惰性執(zhí)行)
  • 把不純的操作交給調(diào)用者來處理

const fp = require('lodash/fp')

class IO {
    static of(x){
        return new IO(function(){
            return x
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
}

// 調(diào)用
let r = IO.of(process).map(p => p.exexPath)
console.log(r._value)

Monad 函子

  • 是可以變扁的Pointed函子盏浙,IO(IO(x))
  • 一個(gè)函子如果具有join 和 of兩個(gè)方法眉睹,并遵守一些定律就是一個(gè)Monad
const fp = require('lodash/fp')

class IO {
    static of(x){
        return new IO(function(){
            return x
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value))
    }
    join() {
        return this._value()
    }
    flatMap(fn){
        return this.map(fn).join()
    }
}
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 = readFileSync('package.json')
        .flatMap(print)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荔茬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竹海,更是在濱河造成了極大的恐慌慕蔚,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件站削,死亡現(xiàn)場(chǎng)離奇詭異坊萝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)许起,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門十偶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人园细,你說我怎么就攤上這事惦积。” “怎么了猛频?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵狮崩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鹿寻,道長(zhǎng)睦柴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任毡熏,我火速辦了婚禮坦敌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痢法。我一直安慰自己狱窘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布财搁。 她就那樣靜靜地躺著蘸炸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尖奔。 梳的紋絲不亂的頭發(fā)上搭儒,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音提茁,去河邊找鬼仗嗦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甘凭,可吹牛的內(nèi)容都是我干的稀拐。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼丹弱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼德撬!你這毒婦竟也來了铲咨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤蜓洪,失蹤者是張志新(化名)和其女友劉穎纤勒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隆檀,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摇天,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恐仑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泉坐。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖裳仆,靈堂內(nèi)的尸體忽然破棺而出腕让,到底是詐尸還是另有隱情,我是刑警寧澤歧斟,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布纯丸,位于F島的核電站,受9級(jí)特大地震影響静袖,放射性物質(zhì)發(fā)生泄漏觉鼻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一队橙、第九天 我趴在偏房一處隱蔽的房頂上張望坠陈。 院中可真熱鬧,春花似錦喘帚、人聲如沸畅姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朱嘴,卻和暖如春倾鲫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萍嬉。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工乌昔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壤追。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓磕道,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親行冰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溺蕉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361