函數式編程—3—柯里化、函數組合、FP模塊

柯里化

  • 當一個函數有多個參數的時候可以先傳遞一部分參數調用它(這部分參數以後永遠不變)
  • 然後返回一個新的函數接收剩餘的參數,返回結果

使用柯里化解決硬編碼問題

簡單演示

// 硬編碼問題
function cheackAge(age){
    let min =18 //此處存在硬編碼
    return age>=min
}
console.log(cheackAge(21)) // true

提出硬編碼,作爲參數解決。基礎值需要定義多次

function cheackAge(min,age){
    return age>=min
}
console.log(cheackAge(18,21)) //true
console.log(cheackAge(18,23)) //true

普通版本柯里化

function cheackAge(min){
    return function(age){
        return age>=min
    }
}
const checkAge18=cheackAge(18)
console.log(checkAge18(21)) //true

es6版本柯里化

const cheackAge = (min)=>(age)=>age>=min
const checkAge18=cheackAge(18)
const checkAge13=cheackAge(13)
console.log(checkAge18(21)) //true
console.log(checkAge13(16)) //true

loadsh中的柯里化——curry

創建一個函數,該函數接收一個或多個func的參數,如果func所需要的參數都被提供則執行func並返回結果,否則會繼續返回該函數並等待接收剩餘的參數。

const _ = require('loadsh')
const getSum = (a,b,c)=>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

案例-判斷一個字符串中是否有空白字符

// 定義正則與字符比對的函數
const match = _.curry((reg,str)=>str.match(reg)) 
// 定義判斷是否包含空格的函數
const hasSpace = match(/\s+/g)
// 定義判斷是否包含數字的函數
const hasNumber = match(/\d+/g)
// 定義數組過濾的函數
const filter = _.curry((fn,arr)=>arr.filter(fn))
// 查找數組中包含空格的元素
const findSpace = filter(hasSpace)

console.log(hasSpace('hello word')) // [ ' ' ]

console.log(hasNumber('hello 123abc')) // [ '123' ]

console.log(filter(hasSpace,['hello word','John_Donne'])) // [ 'hello word' ]

console.log(findSpace(['hello word','John_Donne'])) // [ 'hello word' ]

loadsh中的柯里化——模擬實現curry

普通版本

function curry(fn){
    return function curriedFn(){
        var args = [].slice.call(arguments)
        if(args.length<fn.length){
            return function(){
                return curriedFn.apply(undefined,args.concat([].slice.call(arguments)))
            }
        }
        return fn.apply(undefined,args)
    }
}
const getSum = (a,b,c)=>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
console.log(curried(1)(2)(3)) //6

es6版本

const curry=(fn)=>{
    const curriedFn = (...args)=>{
        if(args.length<fn.length){
            return (..._args)=>{
                return curriedFn(...[...args,..._args])
            }
        }
        return fn(...args)
    }
    return curriedFn
}

const getSum = (a,b,c)=>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
console.log(curried(1)(2)(3)) //6

函數柯里化總結

  • 柯里化可以讓我們給一個函數傳遞較少的參數的到一個已經記住某些固定參數的新函數
  • 這是一種對函數參數的“緩存”
  • 讓函數變得更靈活,讓函數的顆粒度更小
  • 可以把多元函數編程一元函數,可以組合使用函數產生強大的功能

函數的組合

  • 純函數和柯里化很容易寫出洋蔥代碼

    h(g(f(x)))

    獲取數組最後一個元素在轉大寫 .toUpper(.first(_.reverse(array)))

  • 函數的組合可以讓我們把細粒度的函數重新組合成一個新的函數

函數組合概念

* 如果一個函數要經過多個函數處理才能得到最終的值,這時候可以吧中間過程的函數合併成一個函數
* 函數就是數據的管道,函數的組合就是吧這些管道連接起來,讓數據穿過多個管道行程最終的結果
* 函數某人是從右向左執行的

函數組合簡單實現——普通代碼

function compose (f,g){
    return function(val){
        return f(g(val))
    }
}
// 數組反轉
function reverse (arr){
    return arr.reverse()
}
// 獲取數組第一個值
function first(arr){
    return arr[0]
}
// 獲取數組中最後的一個元素
const last = compose(first,reverse)

console.log(last([1,2,3])) // 3

函數組合簡單實現——ES6

const compose = (f,g)=>val=>f(g(val))

const reverse = arr=>arr.reverse()

const first = arr=>arr[0]

const last = compose(first,reverse)

console.log(last([1,2,3])) // 3

Lodash中的組合函數

  • flow() 從左到右執行
  • flowRight() 從右到左執行(使用的更多些)

flowRight()

const _ = require('loadsh')

const reverse = arr => arr.reverse()

const first = arr => arr[0]

const toUpper = str => str.toUpperCase()

const fn = _.flowRight(toUpper,first,reverse)

console.log(fn(['one','tow','three'])) // THREE

flow

const _ = require('loadsh')

const reverse = arr => arr.reverse()

const first = arr => arr[0]

const toUpper = str => str.toUpperCase()
// 此處有變化
const fn = _.flow(reverse,first,toUpper)

console.log(fn(['one','tow','three'])) // THREE

模擬實現flowRight

常規實現

const flowRight = function(){
    const args = [].slice.call(arguments)
    return function(val){
        return args.reverse().reduce(function(_val,fn){
            return fn(_val)
        },val)
    }
}

const reverse = arr => arr.reverse()

const first = arr => arr[0]

const toUpper = str => str.toUpperCase()

const fn = flowRight(toUpper,first,reverse)

console.log(fn(['one','tow','three'])) // THREE

ES6實現


const flowRight = (...args)=>val=>args.reverse().reduce((_val,fn)=>fn(_val),val)

const reverse = arr => arr.reverse()

const first = arr => arr[0]

const toUpper = str => str.toUpperCase()

const fn = flowRight(toUpper,first,reverse)

console.log(fn(['one','tow','three'])) // THREE

函數組合結合律

函數的組合要滿足結合律例如 (fn1+fn2)+fn3 == fn1+(fn2+fn3)

const _ = require('loadsh')

console.log(_.flowRight(_.toUpper,_.first,_.reverse)([ 'one', 'two','three'] )) //THREE
console.log(_.flowRight(_.flowRight(_.toUpper,_.first),_.reverse)([ 'one', 'two','three'] )) //THREE
console.log(_.flowRight(_.toUpper,_.flowRight(_.first,_.reverse))([ 'one', 'two','three'] )) //THREE

注意 loadsh 中的 _.reverse 會改變原數組

函數組合如何調試

在管道中增加了一段透明的管子,方便調試

// 實現功能
// my name is tom 轉化爲 MY-NAME-IS-TOM
const _ = require('loadsh')
// 實現打印功能
const log = _.curry((str,data)=>{
    console.log(`${'='.repeat(4)}${str}打印開始${'='.repeat(4)}`)
    console.log(data)
    console.log(`${'='.repeat(4)}${str}打印結束${'='.repeat(4)}`)
    return data
})
// 柯里化split
const split = _.curry((sep,str)=>_.split(str,sep))
// 柯里化map
const map = _.curry((fn,arr)=>_.map(arr,fn))
// 柯里化join
const join = _.curry((sep,arr)=>_.join(arr,sep))

const fn = _.flowRight(join('-'),log('map'),map((val)=>_.toUpper(val)),log('split'),split(' '))

console.log(fn('my name is tom'))
// ====split打印開始====
// [ 'my', 'name', 'is', 'tom' ]
// ====split打印結束====
// ====map打印開始====
// [ 'MY', 'NAME', 'IS', 'TOM' ]
// ====map打印結束====

// MY-NAME-IS-TOM

fp模塊

  • 提供了使用的對函數式編程友好的方法
  • 提供了不可變的auto-curried iteratee-fitst data-last 的方法

loadsh/fp模塊與loadsh對比

loadsh

數據優先函數置後

const _ = require('loadsh')
console.log(_.map(['a', 'b', 'c', 'd'], _.toUpper))
//[ 'A', 'B', 'C', 'D' ]
console.log(_.map(['a', 'b', 'c', 'd']))
//[ 'a', 'b', 'c', 'd' ]
console.log(_.split('hello_word', '_'))
//[ 'hello', 'word' ]

loadsh/fp

函數優先數據置後

const fp = require('loadsh/fp')
console.log(fp.map(fp.toUpper,['a', 'b', 'c', 'd']))
//[ 'A', 'B', 'C', 'D' ]
console.log(fp.map(fp.toUpper)(['a', 'b', 'c', 'd']))
//[ 'A', 'B', 'C', 'D' ]
console.log(fp.split('_','hello_word'))
//[ 'hello', 'word' ]

關於調試時的案例通過fp優化

// 實現功能
// my name is tom 轉化爲 MY-NAME-IS-TOM
const fp = require('loadsh/fp')

const fn = fp.flowRight(fp.join('-'),fp.map((val)=>fp.toUpper(val)),fp.split(' '))
console.log(fn('my name is tom')) // MY-NAME-IS-TOM
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章