函数式编程—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

fp.map模块 与 _map

实现一个小功能,将 [‘1’,‘2’,‘3’,‘4’] 转化为 [ 1, 2, 3, 4 ]

loadsh/map
const _ = require('loadsh')
console.log(_.map(['1','2','3','4'],parseInt)) 
// [ 1, NaN, NaN, NaN ]

原因分析

  • map会传三个参数到parseInt参数中分别是value|值,key|键名or下标,collection|集合(数组,对象等)
  • parseInt接收传入两个参数 第一个数传入数字 第二个是 进制

接管执行过程

const _ = require('loadsh')
_.map(['1','2','3','4'],(val,index,arr)=>{
    // 执行过程如下 会将三个参数都传入
    return parseInt(val,index,arr) 
})
//[ 1, NaN, NaN, NaN ]

改写执行过程

const _ = require('loadsh')
_.map(['1','2','3','4'],(val,index,arr)=>{
    // 只传入一个参数
    return parseInt(val) 
})
// [ 1, 2, 3, 4 ]
loadsh/fp/map
  • fp.map 特性 迭代器以一个参数为上限:
const fp = require('loadsh/fp')
console.log(fp.map(parseInt)(['1', '2', '3', '4'])) //[ 1, 2, 3, 4 ]
console.log(fp.map(parseInt, ['1', '2', '3', '4'])) //[ 1, 2, 3, 4 ]

分下下执行过程以及一个参数为上限的含义

fp.map((val,index)=>{
    console.log(val,index)
    // 1 undefined
    // 2 undefined
    // 3 undefined
    // 4 undefined
},['1', '2', '3', '4'])

fp.map 回调函数只传递了一个参数,所以就不会引发 parseInt 与预期结果不相同的问题了

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵忠洋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值