偏函数和柯里化

偏函数和柯里化解决的最核心的两个问题:

  • 函数组合链中的多元参数的问题
  • 函数的逻辑复用的问题

回顾一下上一篇的函数们,不难发现,它们都是一元函数,这种属于最理想的情况了,但是实际开发中,往往在处理的过程中会夹杂进来其他的参数,比如这样:

function multiply(x, y) {
  return x*y
}

此时我们没办法使用之前的pipe方法了,所以我们的想法当然是,能不能把多餐函数通过某种方式转位单参函数,也就是参数对齐。

任何时候,只要我们想对函数的入参数量进行改造,必须先想到柯里化&偏函数。

柯里化

柯里化就是把一个n元函数,改造为n个相互嵌套的一元函数的过程。

举个例子:

function addThreeNum(a, b, c) {
  return a+b+c
}

正常调用就是:addThreeNum(1, 2, 3)

但是柯里化之后,调用方式变成了这样:addThreeNum(1)(2)(3)

// 将原函数改造为三个嵌套的一元函数
function addThreeNum(a) {
  // 第一个函数用于记住参数a
  return function(b) {
    // 第二个函数用于记住参数b
    return function(c) {
      // 第三个函数用于执行计算
      return a+b+c
    }
  }
}

// 输出6,输出结果符合预期
addThreeNum(1)(2)(3)

但是这种方式其实是改变了addThreeNum这个方法,有没有一种方式,不改变方法本身,还能达到同样的效果呢?

有的,那就是使用高阶函数。

// 定义高阶函数 curry
function curry(addThreeNum) {
  // 返回一个嵌套了三层的函数
  return function addA(a) {
    // 第一层“记住”参数a
    return function addB(b) {
      // 第二层“记住”参数b
      return function addC(c) {
        // 第三层直接调用现有函数 addThreeNum
        return addThreeNum(a, b, c)
      }
    }
  }
}

// 借助 curry 函数将 add
const curriedAddThreeNum = curry(addThreeNum)
// 输出6,输出结果符合预期
curriedAddThreeNum(1)(2)(3)

偏函数

柯里化是将n元函数变为n个一元函数。

偏函数说的是一个 n 元函数变成一个 m(m < n) 元函数。

对于柯里化来说,不仅函数的元发生了变化,函数的数量也发生了变化(1个变成n个)。

对于偏函数来说,仅有函数的元发生了变化(减少了),函数的数量是不变的。

那么怎样使用偏函数实现参数对齐呢?

function multiply(x, y) {
		return x * y
	}

function displayMultiply(fun, num) {
		function wrapperFun(inputNum) {
			return fun(inputNum, num)
		}
		return wrapperFun
	}
	const multiply3 = displayMultiply(multiply, 3)
	console.log(multiply3(2))

其实偏函数和柯里化都是“调整函数的元”的问题。

那怎样实现自动柯里化?自动完成这个套完的过程呢?

我们来分析一下,也就是说,不管我的函数传入多少参数,curry都应该分析出参数的数量,并且动态的根据参数的数量,自动的做嵌套。

我们简单的来拆分一下这个函数的任务:

1.计算出传入参数的数量。

2.根据数量,自动写出递归的层数。

3.在嵌套的最后一层,调用回调函数,传入所有参数。

// curry 函数借助 Function.length 读取函数元数
function curry(func, arity=func.length) {
  // 定义一个递归式 generateCurried
  function generateCurried(prevArgs) {
    // generateCurried 函数必定返回一层嵌套
    return function curried(nextArg) {
      // 统计目前“已记忆”+“未记忆”的参数
      const args = [...prevArgs, nextArg]  
      // 若 “已记忆”+“未记忆”的参数数量 >= 回调函数元数,则认为已经记忆了所有的参数
      if(args.length >= arity) {
        // 触碰递归边界,传入所有参数,调用回调函数
        return func(...args)
      } else {
        // 未触碰递归边界,则递归调用 generateCurried 自身,创造新一层的嵌套
        return generateCurried(args)
      }
    }
  }
  // 调用 generateCurried,起始传参为空数组,表示“目前还没有记住任何参数”
  return generateCurried([])
}

简洁写法:

function curry2(fn) {
  return function curried(...args) {
    if (args.length < fn.length) {
      return function () {
        return curried(...args, ...arguments)
      }
    }
    return fn(...args)
  }
}

举个例子

function add(a, b) {
  return a + b
}

function multiply(a, b, c) {
  return a*b*c
}

function addMore(a, b, c, d) {
  return a+b+c+d
}

function divide(a, b) {
  return a/b
}

我们需要首先对四个函数分别作“一元化”处理。

这“一元化”处理的第一步,就是借助 curry 函数把它们各自的传参方式重构掉:

const curriedAdd = curry(add)
const curriedMultiply = curry(multiply)
const curriedAddMore = curry(addMore)
const curriedDivide = curry(divide)

然后对这些函数逐个传参,传至每个函数只剩下一个待传参数为止。这样,我们就得到了一堆一元函数:

const compute = pipe(
  curriedAdd(1), 
  curriedMultiply(2)(3), 
  curriedAddMore(1)(2)(3), 
  curriedDivide(300)
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值