webVueBlog/mini-vue

3.手写柯里化函数

webVueBlog opened this issue · 0 comments

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

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

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2) // 3
const multiArgFunction = (a, b, c) => a + b + c;
console.log(multiArgFunction(1, 2, 3)); // 6

const curryUnaryFunction = (a) => (b) => (c) => a + b + c;
curryUnaryFunction(1); // returns a function: b => c =>  1 + b + c
curryUnaryFunction(1)(2); // returns a function: c => 3 + c
curryUnaryFunction(1)(2)(3); // returns the number 6

手写柯里化函数

// 手写
function curry(fn, ...args) {
 // fn原函数
 // ...args可以传入初始参数
 // 返回一个函数

 return function() {
  // 缓存目前接收到的参数
  let _args = [...args, ...arguments];

  // 原函数应该接收的参数个数
  let len = fn.length;

  // 比较目前的参数累计与原函数应该接收的参数
  if(_args.length < len) {
   // 代表需要继续返回一个新函数
   // 使用递归,形成闭包,保证函数的独立,不受影响
   return curry(fn, ..._args);
  } else { 
   // 参数累计够了,执行原函数返回结果
   return fn.apply(this, _args);
  }
 }
}

干净版本:

function curry(fn, ...args) {
 const { length } = fn

 return function() {
  const _args = [...args, ...arguments]
  
  if (_args.length < length) return curry(fn, ..._args)

  return fn.apply(this, _args)
 }
}

es6实现:

const curry = (fn, ...args) =>
 fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);

带占位的封装

function curry(fn, args = [], holes = []) {
  const { length } = fn;return function (...args2) {
    const _args = args.slice(0); //存放组合后的参数
    const _holes = holes.slice(0);
    const argsLen = args.length; //fn的实参的长度
    const holesLen = holes.length;
    let index = 0;for (let i = 0; i < args2.length; i++) {
      let arg = args2[i];

      // 处理类似 fn(1, _, _, 4)(_, 3) 这种情况,index 需要指向 holes 正确的下标
      if (arg === _ && holesLen) {
        index++;
        if (index > holesLen) {
          _args.push(arg);
          _holes.push(argsLen - 1 + index - holesLen);
        }
      }

      // 处理类似 fn(1)(_) 这种情况
      else if (arg === _) {
        _args.push(arg);
        _holes.push(argsLen + i);
      }

      // 处理类似 fn(_, 2)(1) 这种情况
      else if (holesLen) {
        // fn(_, 2)(_, 3)
        if (index >= holesLen) _args.push(arg);

        // fn(_, 2)(1) 用参数 1 替换占位符
        else {
          _args.splice(_holes[index], 1, arg);
          _holes.splice(index, 1);
        }
      }

      else _args.push(arg);}if (_holes.length || _args.length < length) return curry.call(this, fn, _args, _holes);

    return fn.apply(this, _args);
  }
}

// test
const _ = {};

const fn = curry(function(a, b, c, d, e) {
    console.log([a, b, c, d, e]);
});

// 验证 输出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5);

柯里化:将使用多个参数的函数转化为一系列使用一个参数的函数

作用:延迟计算,参数复用,提高通用型和适配性

function curry(fn) {
 let length = fn.length;
 let curArg = Array.prototype.slice.call(arguments, 1) || [];
 
 return function() {
  let fnArg = Array.prototype.slice.call(arguments);
  let currArg = currArg.concat(fnArg);
  // 如果现在的参数小于所需参数的话,说明还不够,继续使用柯里化的函数,处理
  if (currArg.length < length) {
   return curry(fn, ...currArg);
  } else {
   return fn.apply(this, currArg);
  }
 };
}
function CurryHelper(fn: Function, ...args: any[]) {
  // 返回一个 包裹执行函数的函数;等待参数满后被调用
  return function (...innerArgs: any[]) {
    // 执行被柯里化的函数
    return fn.apply(this, [...args, ...innerArgs]);
  }
}

function Curry(fn: Function, length?: number) {
  length = length || fn.length

  return function (...args: any[]) {
    if (args.length < length) {
      // 收集 function 和 参数
      const combined = [fn, ...args]
      // 收集 剩余参数长度
      const residueLen = length - args.length
      // 收集 待执行的函数
      const CurryHelperReturn = CurryHelper.apply(this, combined)

      // 参数未满,继续被调用。
      return Curry(CurryHelperReturn, residueLen)
    } else {

      // 参数已满,执行函数
      return fn.apply(this, args)
    }
  }
}

export {
  Curry
}

羽哥:

// 第三版
function curry(fn, args, holes) {
    length = fn.length;

    args = args || [];

    holes = holes || [];

    return function() {

        var _args = args.slice(0),
            _holes = holes.slice(0),
            argsLen = args.length,
            holesLen = holes.length,
            arg, i, index = 0;

        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            // 处理类似 fn(1, _, _, 4)(_, 3) 这种情况,index 需要指向 holes 正确的下标
            if (arg === _ && holesLen) {
                index++
                if (index > holesLen) {
                    _args.push(arg);
                    _holes.push(argsLen - 1 + index - holesLen)
                }
            }
            // 处理类似 fn(1)(_) 这种情况
            else if (arg === _) {
                _args.push(arg);
                _holes.push(argsLen + i);
            }
            // 处理类似 fn(_, 2)(1) 这种情况
            else if (holesLen) {
                // fn(_, 2)(_, 3)
                if (index >= holesLen) {
                    _args.push(arg);
                }
                // fn(_, 2)(1) 用参数 1 替换占位符
                else {
                    _args.splice(_holes[index], 1, arg);
                    _holes.splice(index, 1)
                }
            }
            else {
                _args.push(arg);
            }

        }
        if (_holes.length || _args.length < length) {
            return curry.call(this, fn, _args, _holes);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

var _ = {};

var fn = curry(function(a, b, c, d, e) {
    console.log([a, b, c, d, e]);
});

// 验证 输出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5)

高颜值写法:

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