函数式编程——柯里化

本文深入探讨了函数柯里化的概念,通过实例展示了如何使用柯里化来解决函数中硬编码的问题,以及如何在JavaScript中实现柯里化。文章还讨论了在遇到函数形参默认值时柯里化可能遇到的问题,并分析了lodash库如何处理这种情况。最后,总结了柯里化在函数式编程中的优势,如参数缓存、提高灵活性和转换多元函数等。
摘要由CSDN通过智能技术生成

柯里化

接上篇函数式编程中副作用的代码

function checkAge (age) {
    let min = 18
    return age >= min
}

目前代码中是存在硬编码的 min ,想要解决这种硬编码的方式也很简单,只要把硬编码的方式提取到参数中就好了

function checkAge(min,age){
    return age >= min
}

//test
console.log(checkAge(18,15));
console.log(checkAge(18,20));
console.log(checkAge(20,22));

但是这样又有了一个新的问题,如果是重复的 min 值,我们需要重复调用。这时可以通过闭包来解决

function checkAge(min){
    return function(age){
        return age >= min
    }
}

//es6写法
let chekAge = min => ( age => age >= min )

// test
const checkAge18 = checkAge(18);
console.log(checkAge18(20));

上述就是简单实现函数柯里化的过程

定义(Currying)

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变),
  • 然后返回一个新的函数接收剩余的参数,返回结果

Lodash中的柯里化

_.curry(func)

  • 功能:创建一个函数,该函数接收一个或多个 func 的参数,如果 func 所需要的参数都被提 供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
  • 参数:需要柯里化的函数
  • 返回值:柯里化后的函数
使用
// demo1
const _ = require("lodash");

const add = (a, b, c) => a + b + c;
const curried = _.curry(add);

// test
console.log(add(1, 2, 2))
console.log(curried(1)(2)(3))
console.log(curried(1, 2)(3))
console.log(curried(1,)(2, 3))
console.log(curried(1, 2)) //有兴趣可以看下这行输出什么
案例

提取字符串中的全部空白字符或数字

// demo2
''.match(/\s+/g)
''.match(/\d+/g)

const _ = require("lodash");
const match = _.curry((reg, str) => str.match(reg));

const haveSpace = match(/\s+/g);
const haveNumber = match(/\d+/g);

//test
console.log(haveNumber('46815dddd'));
console.log(haveSpace("sss"))
console.log(haveSpace("  sss"))

const filter = _.curry((func, arry) => arry.filter(func));
const findSpace = filter(haveSpace);

// 过滤数组中包含空格的元素
console.log(findSpace(["  ff","sss","dd0  "]))

原理

  • 柯里化后的函数,如果传入全部参数,立即执行函数
  • 如果传入部分参数,返回一个新的函数,等待传递参数完整后,立即执行函数
// demo3
const add = (a, b, c) => a + b + c;

function curry(func) {
    // 判断实参和形参的个数
    return function curriedFn(...args) {
        if (args.length < func.length) {
            // 形参args个数,少于实参个数(方法名.length)时,返回新函数
            return function () {
                // 将当前的argument和上次的rest参数args,合并递归调用匿名函数
                // arguments是伪数组,通过Array.from转为数组,并将数组展开
                return curriedFn(...args.concat(Array.from(arguments)))
            }
        } else {
            // 传入全部参数,立即执行函数
            return func(...args)
        }
    }
}
const curryEs6 = (func) => {
    const curriedFn = (...args) => {
        if (args.length < func.length) {
            // 箭头函数不可以继续使用arguments,但是可以使用 rest 参数来代替arguments
            return (...values) => curriedFn(...args.concat(values))
        } else {
            return func(...args)
        }
    }
    return curriedFn;
}

const curried = curry(add);
const curried2 = curryEs6(add);

console.log(curried(1)(2)(3))
console.log(curried(1, 2)(3))
console.log(curried(1,)(2, 3))
console.log(curried(1, 2))

console.log(curried2(1)(2)(3))
console.log(curried2(1, 2)(3))
console.log(curried2(1,)(2, 3))
console.log(curried2(1, 2))

思考

在 ES6 中,函数新增了形参默认值,然而当函数使用形参默认值时,rest 参数的length属性,将获取不到设置了默认值的变量,此时如果去做柯里化会发生什么呢?

实验后出现如下结果:

// demo3代码,修改add方法为:
const add = (a, b=2, c) => a + b + c;

// Run Result

console.log(curried(1)(2)(3))
                      ^
TypeError: curried(...) is not a function
    at Object.<anonymous> (D:\xxx\03.custom.js:34:23)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

那么lodash是如何处理的呢,能否避过这个问题呢,使用lodash的实验结果如下:

// demo3代码,修改add方法为:
const add = (a, b=2, c) => a + b + c;

// Run Result

console.log(curried(1)(2)(3))
                      ^

TypeError: curried(...) is not a function
    at Object.<anonymous> (D:\xxx\03-柯里化.js:8:23)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

lodash的结果和我们的结果是一致的!

为什么使用函数形参默认值的时候,会出现这样的情况呢?

回到函数式编程上,使用函数形参默认值的形式,相当于在代码中进行了参数硬编码,如同文章开头写的这样,而柯里化就是为了避免这种情况,出发点是矛盾的,因此报错是符合预期的行为。

function checkAge (age) {
    let mini = 18
    return age >= mini
}

总结

  • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数(lodash中的柯里化—>案例)
  • 这是一种对函数参数的’缓存’
  • 让函数变的更灵活,让函数的粒度更小
  • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能

通过纯函数和柯里化,我们已经能写出符合函数式编程要求的代码,但是还会存在一些小问题,下篇接着写

相关资料

0. ES6 function.length

1. ES6 rest参数

2. ES6 箭头函数

2. 尾调用,尾递归

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值