相似的 curry 函数产生不同的结果

Similar curry functions producing different results

我正在学习函数式 javascript,我遇到了 curry 函数的两种不同实现。我试图了解两者之间的区别,它们看起来很相似,但其中一个在某些情况下工作不正确,而在其他情况下工作正确。

我试过交换使用 es6 定义的函数 'const' 适用于简单的情况,但是当使用 'filter' 过滤字符串时,结果不正确,但使用整数会产生所需的结果。


//es6 
//Does not work well with filter when filtering strings
//but works correctly with numbers
const curry = (fn, initialArgs=[]) => (
    (...args) => (
        a => a.length === fn.length ? fn(...a) : curry(fn, a)
    )([...initialArgs, ...args])
);

//Regular js
//Works well for all cases
function curry(fn) {
  const arity = fn.length;

  return function $curry(...args) {
    if (args.length < arity) {
      return $curry.bind(null, ...args);
    }

    return fn.call(null, ...args);
  };
}

const match = curry((pattern, s) => s.match(pattern));
const filter = curry((f, xs) => xs.filter(f));

const hasQs = match(/q/i);

const filterWithQs = filter(hasQs);
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]));
//Output:
  //es6:
    [ 'hello', 'quick', 'sand', 'qwerty', 'quack' ]
 //regular:
    [ 'quick', 'qwerty', 'quack' ]

这里的主要区别不是 es6 语法,而是参数如何部分应用于函数。

第一个版本:curry(fn, a)

第二版:$curry.bind(null, ...args)

如果您将第一个版本 (es6) 更改为 fn.bind(null, ...args)

,它仅适用于柯里化的一个步骤(在您的示例中需要)

"Regular js" 版本在 es6 语法中的表示如下(您需要常量在递归调用中为函数命名):

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

如果您将 filter 更改为使用 xs.filter(x => f(x)) 而不是 xs.filter(f) 它将起作用 -

const filter = curry((f, xs) => xs.filter(x => f(x)))

// ...

console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]

这是因为Array.prototype.filter传递了三个(3)个参数给"callback"函数,

  • callback - Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:
    • element - The current element being processed in the array.
    • index (Optional) - The index of the current element being processed in the array.
    • array (Optional) - The array filter was called upon.

您在 filter 中使用的 fmatch(/q/i),因此当它被 Array.prototype.filter 调用时,您将获得三 (3) 个额外参数预期的一 (1)。在 curry 的上下文中,这意味着 a.length 将是四 (4),并且由于 4 === fn.lengthfalse(其中 fn.length2 ),returned 的值为curry(fn, a),这是另一个函数。由于所有函数都被认为是 JavaScript 中的 真实 值,因此 filter 调用 return 所有输入字符串。

// your original code:
xs.filter(f)

// is equivalent to:
xs.filter((elem, index, arr) => f(elem, index, arr))

通过将过滤器更改为使用 ...filter(x => f(x)),我们只允许将一 (1) 个参数传递给回调,因此 curry 将评估 2 === 2,即 true,return 值是计算 match 的结果,return 是预期的 true false.

// the updated code:
xs.filter(x => f(x))

// is equivalent to:
xs.filter((elem, index, arr) => f(elem))

另一种可能更好的选择是将 "es6" curry -

中的 === 更改为 >=
const curry = (fn, initialArgs=[]) => (
    (...args) => (
        a => a.length >= fn.length ? fn(...a) : curry(fn, a)
    )([...initialArgs, ...args])
)

// ...

console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]

这允许你"overflow"函数参数"normally",其中JavaScript没有问题-

const foo = (a, b, c) => // has only three (3) parameters
  console.log(a + b + c)
  
foo(1,2,3,4,5) // called with five (5) args

// still works
// => 6


最后,这是我在过去 curry 写的一些其他方式。我已经测试过它们中的每一个都能为您的问题生成正确的输出 -

通过辅助循环-

const curry = f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (f.length, [])
}

多才多艺 curryN,适用于可变参数函数 -

const curryN = n => f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (n, [])
};

// curry derived from curryN
const curry = f => curryN (f.length) (f)

几天价差 -

const curry = (f, ...xs) => (...ys) =>
  f.length > xs.length + ys.length 
    ? curry (f, ...xs, ...ys)
    : f (...xs, ...ys)

the lambda calculus and Howard Curry's fixed-point Y-combinator致敬-

const U =
  f => f (f)

const Y =
  U (h => f => f (x => U (h) (f) (x)))

const curryN =
  Y (h => xs => n => f =>
    n === 0
      ? f (...xs)
      : x => h ([...xs, x]) (n - 1) (f)
  ) ([])

const curry = f =>
  curryN (f.length) (f)

和我个人的最爱 -

// for binary (2-arity) functions
const curry2 = f => x => y => f (x, y)

// for ternary (3-arity) functions
const curry3 = f => x => y => z => f (x, y, z)

// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f (...xs, ...ys)

最后,@Donat 的答案有一个有趣的转折,支持匿名递归 -

const U =
  f => f (f)

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