我正在学习函数式javascript,并且遇到了curry函数的两个不同实现。我试图了解两者之间的区别,它们看起来相似,但其中一种在某些情况下无法正确运行,而在其他情况下则可以正确运行。
我尝试过交换使用es6'const'定义的功能 适用于简单情况,但使用“过滤器”过滤字符串时,结果不正确,但使用整数可产生所需的结果。
//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' ]
答案 0 :(得分:1)
如果您将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
-函数是谓词,用于测试数组的每个元素。返回true保留元素,否则返回false。它接受三个参数:
element
-数组中正在处理的当前元素。index
(可选)-数组中正在处理的当前元素的索引。array
(可选)-调用了数组过滤器。
您在f
中使用的filter
是match(/q/i)
,因此当Array.prototype.filter
调用它时,您会得到三(3)个额外的参数,而不是预期一(1)。在curry
的上下文中,这意味着a.length
将是四(4),并且由于4 === fn.length
是false
(其中fn.length
是2
),返回值为curry(fn, a)
,这是另一个函数。由于所有函数在JavaScript中都被视为 truthy 值,因此filter
调用将返回所有输入字符串。
// 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
,即{{1} },返回值是评估true
的结果,它返回预期的match
或 true
。
false
另一种可能更好的选择是将“ es6”中的// the updated code:
xs.filter(x => f(x))
// is equivalent to:
xs.filter((elem, index, arr) => f(elem))
更改为===
>=
-
curry
这使您可以“正常”“溢出”功能参数,而JavaScript则没有问题-
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' ]
最后,这是我过去写过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)
向the lambda calculus和Howard Curry的定点Y-combinator致敬-
const curry = (f, ...xs) => (...ys) =>
f.length > xs.length + ys.length
? curry (f, ...xs, ...ys)
: f (...xs, ...ys)
和我的个人收藏-
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)
最后,@ Donat的答案很有趣,可以实现匿名递归-
// 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)
答案 1 :(得分:0)
此处的主要区别不是es6语法,而是参数如何部分应用于函数。
第一版:curry(fn, a)
第二版:$curry.bind(null, ...args)
如果将第一个版本(es6)更改为fn.bind(null, ...args)
,则仅适用于一步一步(如示例所示)工作
es6语法中“ Regular js”版本的表示应如下所示(您需要在常量中为递归调用中的函数命名):
curry = (fn) => {
const c = (...args) => (
args.length < fn.length ? c.bind(null, ...args) : fn(...args)
);
return c;
}