js - 理解函数柯里化

怎么理解柯里化

柯里化的意思就是将一个多元函数,转换成一个依次调用的单元函数。

curry 的这种用途可以理解为:参数复用。本质上是降低通用性,提高适用性。

f(a,b,c)f(a)(b)(c)

var add = function(x) {
  return function(y) {
    return x + y;
  }; 
};
const increment = add(1);

increment(10); // 11
var { curry } = require('lodash')

var abc = function(a, b, c) {
  return [a, b, c];
};
 
var curried = curry(abc);
 
console.log(curried(1)(2)(3));
// => [1, 2, 3]
 
var person = [{name: 'kevin', age: 12 }, {name: 'daisy', age: 13}]

var prop = curry(function (key, obj) {
  return obj[key]
});

// 普通方式
var name = person.map(function (item) {
    return item.name;
})

// 柯里化方式
var name = person.map(prop('name'))
console.log(name) // [ 'kevin', 'daisy' ]

var age = person.map(prop('age'))
console.log(age) // [ 12, 13 ]


image.png

上面代码,我们将返回对象属性的部分通过函数编程实现。

var prop = curry(function (key, obj) {
  return obj[key]
});

curry函数将传入的function内的两个参数。变成两个使用一个参数的函数执行。

function(key, obj) 

// 根据柯里化转换转换成两个函数,并且第一个参数调用之后返回的还是一个函数,参数为原函数的第二个参数。

props(key) => function(obj)

精髓理解:用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数

手写柯里化

初始版本


var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  const fnArgsCount = fn.length;  	// fn函数的形参个数
  let curryArgs = [];			// 柯里化函数已经传入的实惨个数
  
  function wrapperFunction () {
    curryArgs = curryArgs.concat([...arguments])
      
    if (curryArgs.length < fnArgsCount) {
      return wrapperFunction;
     }
      
     return fn.apply(this, curryArgs)
   }
  
   return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]

incre() 				// 返回 [1, 2, 3]


上面的代码可以正常返回,但是还是存在问题。


只能执行一次,incre不能多次执行。这里因为上面书写的方式将参数统一记录下来了。customerCurry参数是最外层必包在维护。所以在incre(1)(2)(3) 执行完之后。内部参数个数已经达到三个


第二版本


var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  	const fnArgsCount = fn.length;  	// fn函数的形参个数
  	let curryArgs = [];								// 柯里化函数已经传入的实惨个数
  
		if (fnArgsCount <= 1) {
    	throw new Error('函数至少传入两个参数');
    }
  
  	function wrapperFunction () {
    	curryArgs = [...arguments];
      
      if (curryArgs.length < fnArgsCount) {
      	return eval(`wrapperFunction.bind(this, ${curryArgs.reduce(function(x, y) {return x + ',' + JSON.stringify(y);})} )`);
      }
      
      return fn.apply(this, curryArgs)
    }
  
  	return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]


当然这种还是有缺陷,因为对于参数的传递,这里处理不是很好,使用的是json的方法。


第三版本


var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  	const fnArgsCount = fn.length;  	// fn函数的形参个数
  	const curryWrapperArgs = [...arguments].slice(1);
  
  	function wrapperFunction () {
    	let curryArgs = [fn, ...curryWrapperArgs, ...arguments];
      
      if (curryArgs.length < fnArgsCount + 1) {
      	return customerCurry.apply(this, curryArgs);
      }
      
      return fn.apply(this, curryArgs.slice(1))
    }
  
  	return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]



第四版本

这样写起来没什么问题,但是既然学到了柯里化,函数式编程的思想去书写一下。

例子来源:https://github.com/mqyqingfeng/Blog/issues/42

// 第二版
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}


执行顺序:
image.png

换一个方向:
image.png
image.png

现在我们用函数式编程的思想来理解一下。

image.pngimage.png

第五版本:高颜值写法

var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)

参照

  • https://github.com/mqyqingfeng/Blog/issues/42
  • https://juejin.cn/post/6844903936378273799#heading-14
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页