相似的 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
中使用的 f
是 match(/q/i)
,因此当它被 Array.prototype.filter
调用时,您将获得三 (3) 个额外参数预期的一 (1)。在 curry
的上下文中,这意味着 a.length
将是四 (4),并且由于 4 === fn.length
是 false
(其中 fn.length
是 2
),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)
)
我正在学习函数式 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
中使用的 f
是 match(/q/i)
,因此当它被 Array.prototype.filter
调用时,您将获得三 (3) 个额外参数预期的一 (1)。在 curry
的上下文中,这意味着 a.length
将是四 (4),并且由于 4 === fn.length
是 false
(其中 fn.length
是 2
),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)
)