我在JavaScript中编写了一个简单的curry
函数,可以在大多数情况下正常工作:
const add = curry((a, b, c) => a + b + c);
const add2 = add(2);
const add5 = add2(3);
console.log(add5(5));

<script>
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried] || initArgs.length >= functor.length)
return functor(...initArgs);
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
</script>
&#13;
但是,它不适用于以下情况:
// length :: [a] -> Number
const length = a => a.length;
// filter :: (a -> Bool) -> [a] -> [a]
const filter = curry((f, a) => a.filter(f));
// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = curry((f, g, x) => f(g(x)));
// countWhere :: (a -> Bool) -> [a] -> Number
const countWhere = compose(compose(length), filter);
根据以下问题countWhere
定义为(length .) . filter
:
What does (f .) . g mean in Haskell?
因此,我应该能够使用countWhere
,如下所示:
const odd = n => n % 2 === 1;
countWhere(odd, [1,2,3,4,5]);
但是,它不返回3
(数组[1,3,5]
的长度),而是返回一个函数。我做错了什么?
答案 0 :(得分:12)
您的curry
功能(以及JavaScript中的most curry
functions that people write)存在问题它没有正确处理额外的参数。
curry
做什么
假设f
是一个函数而f.length
是n
。让curry(f)
为g
。我们使用g
参数调用m
。会发生什么?
m === 0
,则返回g
。m < n
则将f
部分应用于m
个新参数,并返回一个新的curried函数,该函数接受剩余的n - m
个参数。f
应用于m
参数并返回结果。这是大多数curry
函数所做的,这是错误的。前两种情况是正确的,但第三种情况是错误的。相反,它应该是:
m === 0
,则返回g
。m < n
则将f
部分应用于m
个新参数,并返回一个新的curried函数,该函数接受剩余的n - m
个参数。m === n
,则将f
应用于m
个参数。如果结果是函数,则curry
结果。最后,返回结果。m > n
,则将f
应用于第一个n
参数。如果结果是函数,则curry
结果。最后,将结果应用于剩余的m - n
参数并返回新结果。 大多数curry
功能的问题
请考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
如果我们使用不正确的curry
函数,那么这相当于:
compose(compose(length), filter, odd, [1,2,3,4,5]);
但是,compose
只接受三个参数。最后一个参数被删除:
const compose = curry((f, g, x) =>f(g(x)));
因此,上述表达式的评估结果为:
compose(length)(filter(odd));
这进一步评估为:
compose(length, filter(odd));
compose
函数需要多一个参数,这就是它返回函数而不是返回3
的原因。要获得正确的输出,您需要写:
countWhere(odd)([1,2,3,4,5]);
这就是大多数curry
函数错误的原因。
使用正确的curry
函数
再次考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
如果我们使用正确的curry
函数,那么这相当于:
compose(compose(length), filter, odd)([1,2,3,4,5]);
评估为:
compose(length)(filter(odd))([1,2,3,4,5]);
进一步评估(跳过中间步骤):
compose(length, filter(odd), [1,2,3,4,5]);
结果是:
length(filter(odd, [1,2,3,4,5]));
生成正确的结果3
。
实施正确的curry
功能
在ES6中实现正确的curry
功能非常简单:
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried]) return functor(...initArgs);
const arity = functor.length;
const args = initArgs.length;
if (args >= arity) {
const result = functor(...initArgs.slice(0, arity));
return typeof result === "function" || args > arity ?
curry(result, ...initArgs.slice(arity)) : result;
}
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
我不确定curry
的实施速度有多快。也许有人可以让它更快。
使用正确的curry
功能
使用正确的curry
函数可以直接将Haskell代码转换为JavaScript。例如:
const id = curry(a => a);
const flip = curry((f, x, y) => f(y, x));
id
函数很有用,因为它允许您轻松地部分应用非咖喱函数:
const add = (a, b) => a + b;
const add2 = id(add, 2);
flip
函数很有用,因为它允许您在JavaScript中轻松创建right sections:
const sub = (a, b) => a - b;
const sub2 = flip(sub, 2); // equivalent to (x - 2)
这也意味着您不需要像extended compose
function这样的黑客:
What's a Good Name for this extended `compose` function?
您可以简单地写一下:
const project = compose(map, pick);
正如问题所述,如果您想撰写length
和filter
,那么请使用(f .) . g
模式:
What does (f .) . g mean in Haskell?
另一种解决方案是创建更高阶compose
函数:
const compose2 = compose(compose, compose);
const countWhere = compose2(length, fitler);
由于curry
函数的正确实现,这一切都是可能的。
额外的思考食物
当我想要编写一系列函数时,我通常使用以下chain
函数:
const chain = compose((a, x) => {
var length = a.length;
while (length > 0) x = a[--length](x);
return x;
});
这允许您编写如下代码:
const inc = add(1);
const foo = chain([map(inc), filter(odd), take(5)]);
foo([1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
这相当于以下Haskell代码:
let foo = map (+1) . filter odd . take 5
foo [1,2,3,4,5,6,7,8,9,10]
它还允许您编写如下代码:
chain([map(inc), filter(odd), take(5)], [1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
这相当于以下Haskell代码:
map (+1) . filter odd . take 5 $ [1,2,3,4,5,6,7,8,9,10]
希望有所帮助。
答案 1 :(得分:1)
除了数学定义
currying是将具有
n
参数的函数转换为n
函数序列,每个函数都接受一个参数。因此,arity从n-ary
转换为n * 1-ary
影响编程的影响是什么? 抽象超过arity !
const comp = f => g => x => f(g(x));
const inc = x => x + 1;
const mul = y => x => x * y;
const sqr = x => mul(x)(x);
comp(sqr)(inc)(1); // 4
comp(mul)(inc)(1)(2); // 4
comp
需要两个函数f
和g
以及一个任意参数x
。因此g
必须是一元函数(只有一个形式参数的函数)和f
,因为它的返回值为g
。 comp(sqr)(inc)(1)
工作的任何人都不会感到惊讶。 sqr
和inc
都是一元的。
但是mul
显然是一个二元函数。这怎么样才能起作用?因为currying抽象了mul
的arity。你现在可以想象一下强大的功能是什么。
在ES2015中,我们可以使用箭头功能简洁地预先调整我们的功能:
const map = (f, acc = []) => xs => xs.length > 0
? map(f, [...acc, f(xs[0])])(xs.slice(1))
: acc;
map(x => x + 1)([1,2,3]); // [2,3,4]
尽管如此,我们需要一个程序化的咖喱功能来控制我们无法控制的所有功能。由于我们了解到currying主要是指对arity的抽象,我们的实现不能依赖于Function.length
:
const curryN = (n, acc = []) => f => x => n > 1
? curryN(n - 1, [...acc, x])(f)
: f(...acc, x);
const map = (f, xs) => xs.map(x => f(x));
curryN(2)(map)(x => x + 1)([1,2,3]); // [2,3,4]
明确地将arity传递给curryN
有一个很好的副作用,我们也可以讨论可变参数函数:
const sum = (...args) => args.reduce((acc, x) => acc + x, 0);
curryN(3)(sum)(1)(2)(3); // 6
仍然存在一个问题:我们的咖喱解决方案无法处理方法。好的,我们可以轻松地重新定义我们需要的方法:
const concat = ys => xs => xs.concat(ys);
const append = x => concat([x]);
concat([4])([1,2,3]); // [1,2,3,4]
append([4])([1,2,3]); // [1,2,3,[4]]
另一种方法是以一种可以处理多参数函数和方法的方式调整curryN
:
const curryN = (n, acc = []) => f => x => n > 1
? curryN(n - 1, [...acc, x])(f)
: typeof f === "function"
? f(...acc, x)
: x[f](...acc);
curryN(2)("concat")(4)([1,2,3]); // [1,2,3,4]
我不知道这是否是正确的方法来解决Javascript中的函数(和方法)。这是一种可能的方式。
修改强>
const U = f => f(f);
const curryN = U(h => acc => n => f => x => n > 1
? h(h)([...acc, x])(n-1)(f)
: f(...acc, x))([]);
缺点:实现更难以阅读并且性能下降。
答案 2 :(得分:-1)
//---Currying refers to copying a function but with preset parameters
function multiply(a,b){return a*b};
var productOfSixNFiveSix = multiply.bind(this,6,5);
console.log(productOfSixNFive());
//The same can be done using apply() and call()
var productOfSixNFiveSix = multiply.call(this,6,5);
console.log(productOfSixNFive);
var productOfSixNFiveSix = multiply.apply(this,[6,5]);
console.log(productOfSixNFive);