YvetteLau / Step-By-Step

不积跬步,无以至千里;
705 stars 66 forks source link

编写一个通用的柯里化函数currying #33

Open YvetteLau opened 5 years ago

KRISACHAN commented 5 years ago

常规法

  const currying = fn => {
    if (typeof fn !== 'function') {
      return new Error('No function provided')
    }
    return function curriedFn(...args){
      if (args.length < fn.length) {
        return function () {
          return curriedFn.apply(null, args.concat([].slice.call(arguments)))
        }
      }
      return fn.apply(null, args)
    }
  }

支持多调用法

      const currying = (func, count = func.length, ...args) => count <= args.length ? func(...args) : currying.bind(null, func, count, ...args)
      const sum = (...args) => args.reduce((acc, cur) => acc + cur, 0)
      const add = currying(sum, 2)
      console.log(add(1)(2))
ChasLui commented 5 years ago
  const currying = fn => {
    if (typeof fn !== 'function') {
      return new Error('No function provided')
    }
    return function curriedFn(...args){
      if (args.length < fn.length) {
        return function () {
          return curriedFn.apply(null, args.concat([].slice.call(arguments)))
        }
      }
      return fn.apply(null, args)
    }
  }

再来个左向, 自动柯里化

jinsong5 commented 5 years ago

来个ES5的

var currying = function (fn) {
    if (typeof fn !== 'function') {
        return new Error('need function')
    }
    var args = []
    return function doCurrying() {
        if (arguments.length === 0) {
            return fn.apply(this, args);
        } else {
            [].push.apply(args, arguments);
            return doCurrying;
        }
    }
}
Tcdian commented 5 years ago

类似 lodash 的 curry,支持占位符,支持 new 调用时忽略 this

function curry(func, arity = func.length, guard, partial = []) {
    // guard 守卫, 防止传入多余参数
    partial = guard === void 0 ? partial : []
    const placeholder = curry.placeholder

    const boundFunc = function(...args) {
        const argsLen = args.filter(arg => arg !== placeholder).length
        // _replaceHolders 函数, 处理占位符的情况
        const finalArgs = _replaceHolders(partial, args, placeholder)

        if (argsLen >= arity) {
            // _executeBound 函数, 处理 new 调用boundFunc ,this应被忽略问题
            return _executeBound(func, boundFunc, this, this, finalArgs)
        } else {
            return curry(func, arity - argsLen, void 0, finalArgs)
        }
    }
    return boundFunc
}

// 设置占位符, 默认 '__'
curry.placeholder = '__'

// 整合 partials 和 args 为一个 完整的参数数组, 将partials中的 placeholder替换为 args中元素, args中剩余元素放到 数组结尾
function _replaceHolders(partials, args, placeholder) {
    let separator = 0
    const combinedArgs = partials.map(partial => {
        if (partial === placeholder) {
            return args[separator++]
        }
        return partial
    })
    return [...combinedArgs, ...args.slice(separator)]
}

function _executeBound(func, boundFunc, thisArg, context, args) {
    if (!(context instanceof boundFunc)) {
        return func.call(thisArg, ...args)
    }
    const instance = Object.create(func.prototype)
    const result = func.call(instance, ...args)
    if (isObject(result)) {
        return result
    }
    return instance
}

function isObject(obj) {
    const type = typeof obj
    return obj !== null && (type == 'object' || type == 'function')
}
HappyChenchen commented 5 years ago

函数柯里化是逐步传参、逐步缩小函数的适用范围、逐步求解的过程,它将多变量函数拆解为单变量的多个函数的依次调用。

var currying = function (fn) {
    var args = [];
    return function () {
      if (arguments.length === 0) {
        return fn.apply(this, args);
      } else {
        [].push.apply(args, arguments);
        return arguments.callee;
      }
    }
};

函数柯里化的主要作用和特点:

shenanheng commented 5 years ago
const currying = fn => {
    return function curriedFn(...args){
      if (args.length < fn.length) {
        return function () {
          return curriedFn.apply(null, args.concat([].slice.call(arguments)))
        }
      }
      return fn.apply(null, args)
    }
  }
AILINGANGEL commented 5 years ago
function curry(fn) {
    let oldArgs = Array.prototype.slice.call(arguments, 1);
    return function() {
        let newArgs = Array.prototype.slice.call(arguments);
        let args = newArgs.concat(oldArgs);
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return curry.call(this, fn, ...args);
        }
    }
}
YvetteLau commented 5 years ago

在开始之前,我们首先需要搞清楚函数柯里化的概念。

函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

const currying = (fn, ...args) =>
    args.length < fn.length
        //参数长度不足时,重新柯里化该函数,等待接受新参数
        ? (...arguments) => currying(fn, ...args, ...arguments)
        //参数长度满足时,执行函数
        : fn(...args);
function sumFn(a, b, c) {
    return a + b + c;
}
var sum = currying(sumFn);
console.log(sum(2)(3)(5));//10
console.log(sum(2, 3, 5));//10
console.log(sum(2)(3, 5));//10
console.log(sum(2, 3)(5));//10

函数柯里化的主要作用:

yelin1994 commented 5 years ago

函数柯里化

函数柯里化,就是见一个接受多个参数的函数转化为接受单一参数的函数的技术.代码如下,亲测可用

const cury = function (fn) {
    const args = Array.prototype.slice.call(arguments, 1)
    const argArr = [...args]
    return function currying() {
        argArr.push(...arguments)
        if (argArr.length < fn.length) {
            return cury.call(this, fn, ...argArr)
        } else {
            return fn.call(null, ...argArr)
        }
    }
}
function add(a, b, c) {
   return  a+ b+ c
}
const curyAdd = cury(add)
console.log(curyAdd(1)(2,3))
Cain-kz commented 5 years ago

柯里化函数:一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。 特点:1.延迟计算 2.参数复用 3.动态生成函数的作用

var curryings = function(fn){
    var args = [];
    //存储到curring函数中除了fn之外的其它参数,并存储args函数中
    args = args.concat([].slice.call(arguments,1));
    return function(){
      if(arguments.length===0){
        return fn.apply(this,args);
      }else{
        //将fn中的参数展开,然后在存储到args数组中
        [].push.apply(args,arguments);
      }
    }
  }
  var costss = (function(){
    var money = 0;
    return function () {
      for (var i = 0, l = arguments.length; i < l; i++) {
        money += arguments[i]
      }
      return money;
    }
  })()
  var costst = curryings(costss,100,200)// 转化成 currying 函数
  costss(100,200)
  console.log(costst()) //600
into-piece commented 5 years ago
/**
 * 利用闭包的特性让参数暂时保存,让传入的参数与原函数所需参数数量进行比较,未达到则递归进行存储,待达到则一次性执行
 * @param {*} fn 需要被柯里化的函数
 * @param  {...any} oldArgs  所存入的闭包的参数值
 */
const currying = (fn, ...oldArgs) => (...newArgs) => {
  let args = [...oldArgs, ...newArgs];
  if (args.length >= fn.length) {
    return fn(...args);
  } else {
    return currying(fn, ...args);
  }
};

function sumFn(a, b, c) {
  return a + b + c;
}
var sum = currying(sumFn);
console.log(sum(1)(2)(3));
console.log(sum(1, 2)(3));
console.log(sum(1, 2, 3));
jodiezhang commented 5 years ago
const progressCurrying=(fn,...args) =>{

          var len = fn.length
          //var args = args || []

          return function() {
              var _args = Array.prototype.slice.call(args)
              _args.push(...arguments)

              // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
              if (_args.length < len) {
                  return progressCurrying.call(this,fn,...args,..._args)
              }else{
              // 参数收集完毕,则执行fn
              return fn.apply(this, _args)
              }

          }
      }

      let test =  progressCurrying(function(a, b, c) {
      console.log(a + b + c)
      })
      test(1, 2, 3)
      test(45, 12)(13)
      test(11)(21)(31)
ZadaWu commented 5 years ago

什么是柯里化函数

curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。 你可以一次性地调用 curry 函数,也可以每次只传一个参数分多次调用。

例子:

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

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12

柯里化实现

1- 这里利用的是bind的this参数后面的会使一个函数拥有预设的初始参数。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置

var fn = function () {
    return fn.bind(null, ...aruguments)
}

2- 在1的基础上限制参数的个数,比如你想要的是5个

var numOfRequiredArguments = 5;
var fn = function() {
  if (arguments.length < numOfRequiredArguments) {
    return fn.bind(null, ...arguments);
  } else {
    console.log('we already collect 5 arguments: ', [...arguments]);
    return null;
  }
}
存在的问题:
1. 我们希望将收集到的参数传递给需要它们的目标函数,而不是通过将他们传递给console.log()打印
2. 变量numOfRequiredArguments不应该是写死的,它应该是目标函数所期待的参数个数

3- 因此,除去存储参数以外,我们还需要在某处存储对于目标函数的引用

function magician(targetfn) {
    var numOfArgs = targetfn.length;
    return function fn(){
        if (arguments.length < numOfArgs) {
            return fn.bind(null, ...arguments);
        } else {
            return targetfn.apply(null, arguments);
        }
    }
}

magician 函数的作用是:它接收目标函数作为参数,然后返回‘参数收集器’函数,与上例中 fn 函数作用相同。唯一的不同点在于,当收集的参数数量与目标函数所必需的参数数量相等时,它将把收集到的参数通过 apply 方法给到该目标函数,并返回计算的结果。这个方法通过将其存储在 magician 创建的闭包当中来解决第一个问题(引用目标函数)。

4- 使用magician函数本身作为参数收集器

function magician(targetfn) {
    var numOfArgs = targetfn.length;
    if (arguments.length - 1< numOfArgs) {
        return magician.bind(null, ...arguments)
    } else {
        return targetfn.apply(null, Array.prototype.slice.call(arugments, 1))
    }
}

因为magician接受目标函数作为它的第一个参数,因此收集到的参数将始终包含该函数作为arguments[0]。这就导致,我们在检查有效参数的总数时,需要减去第一个参数。 因为目标函数时递归传递给magician函数的,因此我们可以通过传入第一个参数显示地引用目标函数,以代替使用闭包保存目标函数的引用

MissNanLan commented 5 years ago
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

const sum = (...args) => args.reduce((acc, cur) => acc + cur, 0)
const add = currying(sum, 2)
console.log(add(1)(2))