學(xué)習(xí)筆記-函數(shù)式編程范式

函數(shù)式編程范式

為什么學(xué)習(xí)函數(shù)式編程

  • 函數(shù)式編程是隨著react的流行受到了越來越多的關(guān)注
  • vue 3也開始擁抱函數(shù)式編程
  • 函數(shù)式編程可以拋棄this
  • 打包過程中可以更好的利用tree shaking過濾無用代碼
  • 方便測試和并行處理

當(dāng)前也有很多庫可以幫助我們進(jìn)行函數(shù)式開發(fā)紊馏,eg:lodash奉件,underscore,ramda

函數(shù)式編程概念

函數(shù)式編程(Functional Programming,F(xiàn)P)悦荒,F(xiàn)P是編程范式之一。
編程范式還有:

  • 面向過程編程: 按照步驟县好,一步一步來實(shí)現(xiàn)构哺。
  • 面向?qū)ο缶幊蹋喊熏F(xiàn)實(shí)世界中的事物抽象成程序世界中的類和對(duì)象,通過封裝熬芜,繼承和多態(tài)來演示事物事件的聯(lián)系
  • 函數(shù)式編程:把現(xiàn)實(shí)世界的事物和事物之間的聯(lián)系抽象到程序世界莲镣。

程序的本質(zhì):根據(jù)輸入,經(jīng)過運(yùn)算涎拉,獲得輸出瑞侮。執(zhí)行這個(gè)過程的就是函數(shù)。而函數(shù)式編程就是對(duì)這個(gè)運(yùn)算過程進(jìn)行抽象曼库,但是面向?qū)ο缶幊淌菍?duì)事物進(jìn)行抽象区岗。

函數(shù)式編程中的函數(shù)指的不是程序中的函數(shù)(方法),指的是數(shù)學(xué)中的函數(shù)毁枯,就是一個(gè)映射關(guān)系慈缔,eg: y = sin(x)铺罢。此時(shí)薪捍,相同的輸入始終要得到相同的輸出(純函數(shù))

//非函數(shù)式
let num1 = 1
let add = num1 + 1

//函數(shù)式
function add1(num){
    return num + 1
}
let add = add1(2)

函數(shù)是一等公民

  • 函數(shù)可以存儲(chǔ)在變量中
  • 函數(shù)可以作為參數(shù)
  • 函數(shù)可以作為返回值

eg:

//函數(shù)表達(dá)式就是把函數(shù)賦值給變量
let fun = function (){
    console.log('hello')
}

//改造成賦值給變量
const obj = {
    show(params){ return otherObj.show(params) }
}
//修改后
const obj = {
    show: otherObj.show
}

高階函數(shù)(Higher-order function)

高階函數(shù)就是把函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)登疗,或者把函數(shù)作為另一個(gè)函數(shù)的返回結(jié)果赂韵。

// 利用高階函數(shù)-將函數(shù)作為參數(shù)寫一個(gè)forEach函數(shù)
function forEach(arr, fn) {
  for (let i = 0; i < arr.length; i++) {
    fn(arr[i], i)
  }
}
const arr = [1, 2, 3, 4, 5]
forEach(arr, (item, index) => {
    console.log(item, index)
})

// 利用函數(shù)可以作為返回值娱节,實(shí)現(xiàn)一個(gè)once函數(shù)-只執(zhí)行一次
function once(fn) {
  let mark = false
  return function () {
    if (!mark) {
      mark = true
      fn.apply(this, arguments)
    }
  }
}
let pay = once((money) => {
  console.log(`${money} RMB`)
})
pay(100)
pay(100)
pay(100)
pay(100)

高階函數(shù)的意義:

  • 抽象通用的問題
  • 屏蔽細(xì)節(jié),只關(guān)注我們的目標(biāo)

常用的高階函數(shù):
forEach, filter, map, every, some...

閉包

在把函數(shù)當(dāng)作返回值返回時(shí)祭示,內(nèi)部函數(shù)可以訪問外部函數(shù)中的變量肄满,就形成了閉包。

function outter() {
 let num = 0
 return function inner() {
   console.log(num++)
 }
}
let o = outter()
o()
o()
o()

以上例子,如果outter內(nèi)部沒有閉包稠歉,outter調(diào)用完畢后掰担,outter從執(zhí)行棧上移除,同時(shí)它內(nèi)部的變量num也會(huì)從內(nèi)存中移除怒炸,但是內(nèi)部有了inner引用了變量num带饱,outter從執(zhí)行棧移除后,num因?yàn)檫€被inner引用阅羹,不會(huì)被移除勺疼,即-堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫拧?/p>

純函數(shù)

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

lodash是要給純函數(shù)的功能庫执庐,提供了對(duì)數(shù)組,數(shù)字穷躁,對(duì)象耕肩,字符串,函數(shù)等操作的一些方法问潭。

eg:數(shù)組的slice方法是純函數(shù)猿诸,splice是不純的函數(shù)。

  • slice:返回?cái)?shù)組中的指定部分狡忙,不會(huì)改變?cè)瓟?shù)組梳虽。
  • splice:對(duì)數(shù)組進(jìn)行操作返回該數(shù)組,會(huì)改變?cè)瓟?shù)組灾茁。
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// slice 沒有修改原數(shù)組窜觉,每次返回結(jié)果一樣,是純函數(shù)
console.log(arr.slice(1, 3))  //[ 2, 3 ]
console.log(arr.slice(1, 3))  //[ 2, 3 ]
console.log(arr.slice(1, 3))  //[ 2, 3 ]
// splice 修改了原數(shù)組北专,每次返回結(jié)果不一樣禀挫,是不純的函數(shù)
console.log(arr.splice(1, 3)) //[ 2, 3, 4 ]
console.log(arr.splice(1, 3)) //[ 5, 6, 7 ]
console.log(arr.splice(1, 3)) //[ 8, 9 ]

純函數(shù)的好處

  • 可緩存。因?yàn)榧兒瘮?shù)對(duì)相同的輸入始終有相同的結(jié)果拓颓,所以可以把純函數(shù)的結(jié)果緩存起來语婴。
// 自己實(shí)現(xiàn)一個(gè)類似lodash中的 memoize
function getArea(r) {
  console.log(r)
  return Math.PI * r * r
}
function memoize(f) {
  let cache = {}
  return function () {
    let key = JSON.stringify(arguments)
    cache[key] = cache[key] || f.apply(f, arguments)
    return cache[key]
  }
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4)) // 4 50.26548245743669
console.log(getAreaWithMemory(4)) // 50.26548245743669
console.log(getAreaWithMemory(5)) // 5 78.53981633974483
  • 可測試
  • 并行處理。在多線程環(huán)境(eg:Web Worker)下并行操作共享的內(nèi)存數(shù)據(jù)很可能出現(xiàn)意外驶睦,純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù)砰左,所以在并行環(huán)境下可以任意運(yùn)行純函數(shù)。

純函數(shù)的副作用

eg:

// 不純
let mini = 18
function checkAge(age) {
  return age >= mini
}
// 純的场航,有硬編碼
function checkAge(age) {
  let mini = 18
  return age >= mini
}

如果函數(shù)依賴于外部狀態(tài)缠导,就無法保證輸出相同,就會(huì)帶來副作用溉痢,變得不純僻造。

在函數(shù)內(nèi)部定義一個(gè)變量的值憋他,又會(huì)出現(xiàn)硬編碼。不過這個(gè)可以通過柯里化解決嫡意。

副作用不可完全禁止举瑰,盡可能控制他們?cè)诳煽胤秶鷥?nèi)發(fā)生。

柯里化

使用柯里化可以解決硬編碼的問題蔬螟。

柯里化: 當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候,先傳遞一部分參數(shù)調(diào)用它汽畴,這部分參數(shù)以后永遠(yuǎn)不變旧巾,然后返回一個(gè)新的函數(shù)接受剩余的參數(shù),返回結(jié)果忍些。
eg:

let checkAge = min => (age => age >= min)
let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)

console.log(checkAge18(20))
console.log(checkAge20(24))

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

const _ = require('lodash')
function getSum(a, b, c) {
  return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(1, 2, 3)) //6
console.log(curried(1)(2, 3)) //6
console.log(curried(1)(2)(3)) //6

lodash中的curry方法可以將一個(gè)多元函數(shù)鲁猩,轉(zhuǎn)換為任意多元函數(shù)。

自己實(shí)現(xiàn)一個(gè)lodash.curry

function curry(fn){
  return function a(...args){
    if(args.length < fn.length){
      return function(){
        return a(...args.concat(Array.from(arguments)))
      }
    }
    return fn(...args)
  }
}
function getSum(a, b, c) {
  return a + b + c
}
const curried = curry(getSum)
console.log(curried(1, 2, 3))
console.log(curried(1)(2, 3))
console.log(curried(1)(2)(3))

總結(jié)

  • 柯里化可以返回一個(gè)新函數(shù)罢坝,這個(gè)新函數(shù)已經(jīng)記住了某些固定參數(shù)
  • 這是一種對(duì)函數(shù)參數(shù)的‘緩存’
  • 讓函數(shù)的粒度更小廓握,更靈活
  • 可以把多元函數(shù)轉(zhuǎn)換成一元函數(shù),組合使用函數(shù)產(chǎn)生強(qiáng)大的功能

函數(shù)組合

純函數(shù)和柯里化很容易寫出洋蔥代碼嘁酿,eg:_.toUpper(_.first(_.reverse(arr)))隙券,而以函數(shù)組合的形式 fn = compose(f1, f2, f3) 把中間過程的函數(shù)合并成一個(gè)函數(shù),看起來更簡潔闹司。函數(shù)組合默認(rèn)是從右到左執(zhí)行娱仔,所以執(zhí)行順序是 f3, f2, f1。

// 函數(shù)組合演示
// 洋蔥代碼并沒有被省略游桩,而是被封裝起來了
function compose(f, g) {
  return function (value) {
    return f(g(value))
  }
}
// 實(shí)現(xiàn)將數(shù)組先反轉(zhuǎn)在取第一個(gè)
function reverse(arr) {
  return arr.reverse()
}
function first(arr) {
  return arr[0]
}
const last = compose(first, reverse)
console.log(last([1, 2, 3, 4, 5]))  //5

lodash中的組合函數(shù)

  • flow() 從左到右執(zhí)行
  • flowRight() 從右到左執(zhí)行牲迫,使用的更多
//  使用lodash 中的函數(shù)組合的方法: _.flowRight()
const _ = require('lodash')
//  實(shí)現(xiàn)先翻轉(zhuǎn),再去第一個(gè)借卧,再大寫
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()

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

自己實(shí)現(xiàn)flowRight

function compose(...args) {
  return function (value) {
    return args.reverse().reduce((pre, pro) => {
      return pro(pre)
    }, value)
  }
}

// 改為箭頭函數(shù)
const compose = (...args) => value => args.reverse().reduce((pre, pro) => pro(pre), value)

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = compose(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))  // THREE

函數(shù)組合-結(jié)合律

函數(shù)組合要滿足結(jié)合律盹憎,即:

let associative = comose(compose(f,g),h) == compose(f,compose(g,h)) //true

函數(shù)組合-調(diào)試

函數(shù)組合的方式,如果最終結(jié)果不是預(yù)期铐刘,很難找出是哪一步出了錯(cuò)陪每,eg:

// NEVER SAY DIE  -->  never-say-die
//將左側(cè)字符串轉(zhuǎn)換成右側(cè)的字符串,要經(jīng)過分割滨达,變小寫奶稠,再用-隔開
const log = _.curry((tag, v) => {
  console.log(tag, v)
  return v
})
//為了滿足函數(shù)組合參數(shù)都是一個(gè),將split和join柯里化
// _.toLower()
// _.split()
const split = _.curry((sep, str) => _.split(str, sep))
// _.join()
const join = _.curry((sep, arr) => _.join(arr, sep))

const f = _.flowRight(join('-'), _.toLower, split(' '))
console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e

打印后發(fā)現(xiàn)結(jié)果和預(yù)期不一樣捡遍,但是不好定位錯(cuò)誤位置锌订,我們可以給函數(shù)組合的每個(gè)函數(shù)中間加個(gè)log來打印每次結(jié)果是否正確。

const log = (v) => {
  console.log(v)
  return v
}
const f = _.flowRight(join('-'), _.toLower, log, split(' ')) //[ 'NEVER', 'SAY', 'DIE' ]
const f = _.flowRight(join('-'), log, _.toLower, split(' ')) //never,say,die

可以看到画株,在_.toLower后辆飘,數(shù)組變成了字符串格式啦辐,和預(yù)期不一樣。但是這樣打log蜈项,如果同時(shí)放入多個(gè)log芹关,分不清打印的是哪一步,可以通過改造:

// tag 用來標(biāo)注位置紧卒,v是結(jié)果侥衬,用_.curry柯里化
const log = _.curry((tag, v) => {
  console.log(tag, v)
  return v
})
const f = _.flowRight(join('-'), log('lower 后'), _.toLower, log('split 后'), split(' '))
// split 后 [ 'NEVER', 'SAY', 'DIE' ]
// lower 后 never,say,die

lodash-fp模塊

lodash中的fp模塊提供了實(shí)用的對(duì)函數(shù)式編程友好的方法,并且函數(shù)優(yōu)先跑芳,數(shù)據(jù)滯后轴总,自動(dòng)柯里化。

// lodash 模塊,  都是數(shù)據(jù)優(yōu)先博个,函數(shù)滯后
_.map(['a', 'b', 'c'], _.toUpper) // ['A','B','C']
_.map(['a', 'b', 'c']) // ['a', 'b', 'c']

_.split('Hello World', ' ')

// lodash/fp 模塊,  都是函數(shù)優(yōu)先怀樟,數(shù)組滯后
const fp = require('lodash/fp')

fp.map(fp.toUpper, ['a', 'b', 'c'])
fp.map(fp.toUpper)(['a', 'b', 'c'])

fp.split(' ', 'Hello World')
fp.split(' ')('Hello World')

將前面的例子做修改

const f = fp.flowRight(fp.join('-'),  fp.split(' '),  fp.toLower)
console.log(f('NEVER SAY DIE'))  // never-say-die
lodash 和 lodash/fp 模塊中的map方法的區(qū)別

首先執(zhí)行console.log(_.map(['23','8','10'], parseInt)) // [ 23, NaN, 2 ],想將數(shù)組中的字符串轉(zhuǎn)為數(shù)字盆佣,但是最終拿到的結(jié)果和預(yù)期不一樣往堡,讓我們看一下_.map的解釋:

image.png

函數(shù)接收到的是三個(gè)參數(shù),value,index,array共耍,所以此時(shí)parseInt是這樣執(zhí)行的:

parseInt('23', 0, array)
parseInt('8', 1, array)
parseInt('10', 2, array)

parseInt的第二個(gè)參數(shù)是幾進(jìn)制虑灰,所以此時(shí)結(jié)果不符合預(yù)期。但是看一下fp.map方法:

image.png

可以看到fp.map方法中的函數(shù)只接受一個(gè)參數(shù),console.log(fp.map(parseInt, ['23', '8', '10'])) //[ 23, 8, 10 ],parseInt接受一個(gè)參數(shù)征堪,就不會(huì)出現(xiàn)剛才的問題瘩缆。

Point Free

Point Free是一種編程的風(fēng)格,具體形式就是函數(shù)組合佃蚜。

  • 不需要指明處理的數(shù)據(jù)
  • 只需要合成運(yùn)算規(guī)則
  • 需要定義一些輔助的基本運(yùn)算函數(shù)
    eg:const f = fp.flowRight(fp.join('-'), fp.split(' '), fp.toLower)
    這個(gè)例子中將運(yùn)算過程合成庸娱,沒有指明要處理的數(shù)據(jù)。
// Hello      Word => hello_world
// fp 中的方法都是柯里化的
// fp.replace可以接受3個(gè)參數(shù)谐算,1 匹配的正則熟尉,  2 替換成什么  3 要修改的字符串
// fp.replace是柯里化所以接收兩個(gè)參數(shù)可以返回一個(gè)函數(shù),這個(gè)函數(shù)可以接受一個(gè)字符串
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello      Word')) // hello_word

案例:

// 把一個(gè)字符串中的首字母提取洲脂,轉(zhuǎn)換成大寫斤儿,使用. 作為分隔符
// world wild web ==> W. W. W
const f = fp.flowRight(fp.toUpper, fp.join('. '), fp.map(fp.first), fp.split(' '), fp.replace(/\s+/g, ' '))
// const f = fp.flow(fp.replace(/\s+/g, ' '), fp.split(' '), fp.map(fp.first), fp.join('. '), fp.toUpper)
console.log(f('world  wild  web')) // W. W. W

Functor(函子)

  • 容器:包含值和值的變形關(guān)系(函數(shù))
  • 函子:是一個(gè)特殊的容器,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn)恐锦,該對(duì)象具有map方法往果,map方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理。
// Functor 函子
class Container {
  constructor(value) {
    // 維護(hù)一個(gè)不對(duì)外公布的靜態(tài)變量
    this._value = value
  }
  // map方法通過fn處理值一铅,并通過新的函子返回
  map(fn) {
    return new Container(fn(this._value))
  }
}
// map方法返回的是新的函子陕贮,所以還可以調(diào)用map方法
const lr = new Container(5)
  .map(v => v + 3)
  .map(v => v * 2)

console.log(lr)  // Container { _value: 16 }

上邊的new Container可以通過封裝:

class Container {
  static of(value) {
    return new Container(value)
  }
  constructor(value) {
    // 維護(hù)一個(gè)不對(duì)外公布的靜態(tài)變量
    this._value = value
  }
  // map方法通過fn處理值,并通過新的函子返回
  map(fn) {
    return Container.of(fn(this._value))
  }
}
// map方法返回的是新的函子潘飘,所以還可以調(diào)用map方法
const lr = Container.of(5)
  .map(v => v + 3)
  .map(v => v * 2)

console.log(lr)  // Container { _value: 16 }

總結(jié):

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

MayBe函子

  • 對(duì)編程過程中可能遇到的錯(cuò)誤做相應(yīng)的處理
  • 對(duì)外部的空值情況做處理(控制副作用在允許的范圍)
class MayBe {
  static of(value) {
    return new MayBe(value)
  }
  constructor(value) {
    this._value = value
  }
  // 通過 isNothing 判斷是否為空值
  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(null)
  .map(x => x.toUpperCase())
console.log(r)  // MayBe { _value: null }

但是眶明,以下這種情況,不會(huì)出錯(cuò)筐高,但是什么時(shí)候出現(xiàn)null不知道

let r = MayBe.of('hello world')
  .map(x => x.toUpperCase())
  .map(x => null)
  .map(x=> x.toUpperCase())
console.log(r)  // MayBe { _value: null }

可以通過Either函子來解決

Either函子

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.of(JSON.parse(str))
  } catch (e) {
    return Left.of({ error: e.message })
  }
}

let r1 = parseJson('{name:zs}')
console.log(r1)  // Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let r2 = parseJson('{"name":"zs"}')
  .map(x => x.name.toUpperCase())
console.log(r2) // Right { _value: 'ZS' }

使用Left來處理異常,Right來做正確的操作凯傲。

IO函子

  • IO是input犬辰,output的意思。IO函子中的_value 是一個(gè)函數(shù)冰单,整理把函數(shù)作為值來處理。
  • IO函子可以把不純的動(dòng)作存儲(chǔ)到_value中灸促,延遲執(zhí)行诫欠,包裝它的操作是純的。
  • 把不純的操作交給調(diào)用者來處理浴栽。
// IO 函子
class IO {
  static of(value) {
    // this._value 就是這里的function
    return new IO(function () {
      return value
    })
  }
  constructor(fn) {
    this._value = fn
  }
  map(fn) {
    return new IO(fp.flowRight(fn, this._value))
  }
}
// process - node 進(jìn)程
let r = IO.of(process).map(p => p.execPath)
console.log(r) // IO { _value: [Function (anonymous)] }
console.log(r._value())  // D:\node.js\node.exe
IO函子的問題
const fs = require('fs')
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 cat = fp.flowRight(print, readFile)
// IO(IO(x))
// let r = cat('../../../package.json')._value()  // IO { _value: [Function (anonymous)] }
let r = cat('../../../package.json')._value()._value()
console.log(r)

如果出現(xiàn)函子嵌套IO(IO(x))荒叼,需要拿多層_value才能拿到值。

Monad函子

  • Monad函子是可以變扁的Pointed函子IO(IO(x))
  • 一個(gè)函子如果具有join和of兩個(gè)方法并遵守一些定律就是一個(gè)Monad
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() {
    return this._value()
  }
  flatMap(fn) {
    return this.map(fn).join()
  }
}
const fs = require('fs')
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')
  .map(fp.toUpper)
  .flatMap(print)
  .join()
console.log(r)

Pointed函子

  • Pointed函子是時(shí)下你了of靜態(tài)方法的函子
  • of方法是為了避免使用new來創(chuàng)建對(duì)象典鸡,更深層的含義是of方法用來把值放到上下文Context被廓。

Task函子

Folktale

folktale是一個(gè)標(biāo)準(zhǔn)的函數(shù)是編程庫,但是它和lodash萝玷,ramda不同的是嫁乘,他沒有提供很多功能函數(shù),而是提供了一些函數(shù)是處理的操作球碉,eg:compose蜓斧, curry等,一些函子Task睁冬,Either挎春,MayBe等。

基本使用:

const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
let f = curry(2, (x, y) => {
  return x + y
})
console.log(f(1, 2)) // 3
console.log(f(1)(2))// 3

let n = compose(toUpper, first)
console.log(n(['one', 'two'])) // ONE

folktale 2.x 中的Task和 1.0中的Task區(qū)別很大豆拨,1.0用法接近前邊的例子直奋,下邊一2.3.2來演示Task的使用:

Folktale中Task函子的使用
// Task 處理異步任務(wù)
const fs = require('fs')
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
function readFile(filename) {
  return task(resolver => 
  {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) resolver.reject(er)
      resolver.resolve(data)
    })
  })
}
readFile('../../../package.json')
  .map(split('\n'))
  .map(find(x => x.includes('version')))
  .run()
  .listen({
    onRejected: err => {
      console.log(err)
    },
    onResolved: value => {
      console.log(value)
    }
  })
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市施禾,隨后出現(xiàn)的幾起案子脚线,更是在濱河造成了極大的恐慌,老刑警劉巖拾积,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殉挽,死亡現(xiàn)場離奇詭異丰涉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)斯碌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門一死,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人傻唾,你說我怎么就攤上這事投慈。” “怎么了冠骄?”我有些...
    開封第一講書人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵伪煤,是天一觀的道長。 經(jīng)常有香客問我凛辣,道長抱既,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任扁誓,我火速辦了婚禮防泵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝗敢。我一直安慰自己捷泞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開白布寿谴。 她就那樣靜靜地躺著锁右,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讶泰。 梳的紋絲不亂的頭發(fā)上咏瑟,一...
    開封第一講書人閱讀 50,043評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音峻厚,去河邊找鬼响蕴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惠桃,可吹牛的內(nèi)容都是我干的浦夷。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼辜王,長吁一口氣:“原來是場噩夢啊……” “哼劈狐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呐馆,我...
    開封第一講書人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤肥缔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汹来,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體续膳,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡改艇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坟岔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒兄。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖社付,靈堂內(nèi)的尸體忽然破棺而出承疲,到底是詐尸還是另有隱情,我是刑警寧澤鸥咖,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布燕鸽,位于F島的核電站,受9級(jí)特大地震影響啼辣,放射性物質(zhì)發(fā)生泄漏啊研。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一鸥拧、第九天 我趴在偏房一處隱蔽的房頂上張望悲伶。 院中可真熱鬧,春花似錦住涉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柳爽,卻和暖如春媳握,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磷脯。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來泰國打工蛾找, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赵誓。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓打毛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俩功。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幻枉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 最近開始對(duì)前端知識(shí)體系進(jìn)行一個(gè)系統(tǒng)的擴(kuò)展跟學(xué)習(xí),通過每天定期的學(xué)習(xí)诡蜓,對(duì)一些不常使用的知識(shí)點(diǎn)進(jìn)行了解和補(bǔ)充熬甫,同時(shí)對(duì)已...
    彪悍de文藝青年閱讀 309評(píng)論 0 0
  • 函數(shù)式編程中的函數(shù)指的不是程序中的函數(shù)方法,而是數(shù)學(xué)中的函數(shù)即映射關(guān)系蔓罚,是對(duì)運(yùn)算過程的抽象椿肩,是用來描述數(shù)據(jù)之間的映...
    我是一只小毛毛閱讀 294評(píng)論 0 0
  • 文章內(nèi)容輸出來源:拉勾教育大前端高薪訓(xùn)練營 和自我總結(jié) 學(xué)習(xí)函數(shù)式編程的意義 1.受React的流行而被人們?cè)絹碓?..
    油菜又矮吹閱讀 403評(píng)論 0 0
  • 定義 對(duì)運(yùn)算過程抽象, 描述數(shù)據(jù)(函數(shù))間的映射 一等公民 高階函數(shù) 閉包 高階函數(shù) 抽象可以屏蔽細(xì)節(jié)瞻颂,抽象通用的...
    貳玖是只貓閱讀 380評(píng)論 0 1
  • 拉勾大前端的筆記,僅作為學(xué)習(xí)記錄 課程介紹 為什么學(xué)習(xí)函數(shù)式編程郑象,以及什么是函數(shù)編程 函數(shù)式編程的特性(純函數(shù)贡这,柯...
    yapingXu閱讀 285評(píng)論 0 3