@Aadit,
我发布此内容是因为您对我的答案分享了评论以函数式方式“组合” javascript 中的函数? https://stackoverflow.com/a/30198265/633183我在那篇文章中没有具体讨论柯里化,因为这是一个非常有争议的话题,并不是我想在那里打开的一个真正的蠕虫罐头。
我会谨慎使用措辞"how to 正确地 curry"当您似乎在实施中添加自己的糖和便利时。
不管怎样,抛开所有这些,我真的不打算让这篇文章成为一个争论/好斗的帖子。我希望能够就 JavaScript 中的柯里化进行公开、友好的讨论,同时强调我们方法之间的一些差异。
无需再费周折...
澄清:
Given f
是一个函数并且f.length
is n
. Let curry(f)
be g
。我们称之为g
with m
论据。应该发生什么?你说:
- If
m === 0
然后返回g
.
- If
m < n
然后部分申请f
to the m
新参数,并返回一个新的柯里化函数,该函数接受剩余的n - m
论据。
- If
m === n
然后申请f
to the m
论据。如果结果是函数,则柯里化结果。最后返回结果。
- If
m > n
然后申请f
到第一个n
论据。如果结果是函数,则柯里化结果。最后将结果应用到剩余的m - n
参数并返回新结果。
让我们看一个代码示例@阿迪特·M·沙阿 https://stackoverflow.com/a/27996545/633183的代码实际上是
var add = curry(function(x, y) {
return function(a, b) {
return x + y + a + b;
}
});
var z = add(1, 2, 3);
console.log(z(4)); // 10
这里发生了两件事:
- 您正在尝试支持使用可变参数调用柯里化函数。
- 您会自动柯里化返回的函数
我不认为这里有太多争论的余地,但人们似乎错过了什么currying http://en.wikipedia.org/wiki/Currying实际上是
通过:维基百科
在数学和计算机科学领域,currying是将带有多个参数(或参数元组)的函数的求值转换为求值函数序列的技术,每个都有一个参数...
我将最后一点加粗,因为它非常重要;序列中的每个函数只需要一个单一参数;不是像您建议的那样的可变参数(0、1 或更多)参数。
你在你的文章中也提到了 Haskell,所以我假设你知道 Haskell 不存在带有多个参数的函数这样的东西。 (注意:采用元组的函数仍然只是采用一个参数(即单个元组)的函数。其原因是深刻的,并且为您提供了具有可变参数的函数所无法提供的表达灵活性。
那么让我们重新问最初的问题:应该发生什么?
嗯,当每个函数只接受 1 个参数时,这很简单。任何时候,如果给出超过 1 个参数,它们就会被丢弃。
function id(x) {
return x;
}
当我们打电话时会发生什么id(1,2,3,4)
?当然我们只得到1
回来和2,3,4
被完全忽视。这是:
- JavaScript 是如何工作的
- 维基百科说柯里化应该如何运作
- 我们应该如何实施我们自己的
curry
解决方案
在我们进一步讨论之前,我将使用ES6-style 箭头函数 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions但我还将在这篇文章的底部添加 ES5 的等效项。 (可能是今晚晚些时候。)
另一种柯里化技术
在这种方法中,我们写了一个curry连续返回单参数函数,直到指定所有参数为止的函数
作为这个实施的结果,我们有6多用途功能。
// no nonsense curry
const curry = f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (f.length, [])
}
// demo
let sum3 = curry(function(x,y,z) {
return x + y + z;
});
console.log (sum3 (3) (5) (-1)); // 7
好的,我们已经看到了curry
使用简单的辅助循环实现的技术。它没有依赖项,并且声明性定义不到 5 行代码。它允许部分应用功能,一次 1 个参数,就像柯里化函数应该起作用一样。
没有魔法,没有不可预见的自动柯里化,没有其他不可预见的后果。
但柯里化到底有什么意义呢?
好吧,事实证明,我真的不curry
我写的函数。如下所示,我通常以柯里化形式定义所有可重用函数。所以真的,你只需要curry
当您想要与某些您无法控制的函数进行交互时,这些函数可能来自库或其他东西;其中一些可能有可变接口!
有请curryN
// the more versatile, 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);
// some caveman function
let sumN = function() {
return [].slice.call(arguments).reduce(function(a, b) {
return a + b;
});
};
// curry a fixed number of arguments
let g = curryN (5) (sumN);
console.log (g (1) (2) (3) (4) (5)); // 15
咖喱还是不咖喱?就是那个问题
我们将编写一些示例,其中我们的函数均采用柯里化形式。功能将保持极其简单。每一个都带有1
参数,每个参数都有一个返回表达式。
// composing two functions
const comp = f => g => x => f (g (x))
const mod = y => x => x % y
const eq = y => x => x === y
const odd = comp (eq (1)) (mod (2))
console.log (odd(1)) // true
console.log (odd(2)) // false
Your countWhere
功能
// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x =>
f(g(x))
// mod :: Int -> Int -> Int
const mod = x => y =>
y % x
// type Comparable = Number | String
// eq :: Comparable -> Comparable -> Boolean
const eq = x => y =>
y === x
// odd :: Int -> Boolean
const odd =
comp (eq(1)) (mod(2))
// reduce :: (b -> a -> b) -> b -> ([a]) -> b
const reduce = f => y => ([x,...xs]) =>
x === undefined ? y : reduce (f) (f(y)(x)) (xs)
// filter :: (a -> Boolean) -> [a] -> [a]
const filter = f =>
reduce (acc => x => f (x) ? [...acc,x] : acc) ([])
// length :: [a] -> Int
const length = x =>
x.length
// countWhere :: (a -> Boolean) -> [a] -> Int
const countWhere = f =>
comp (length) (filter(f));
console.log (countWhere (odd) ([1,2,3,4,5]))
// 3
Remarks
那么咖喱还是不咖喱呢?
// to curry
const add3 = curry((a, b, c) =>
a + b + c
)
// not to curry
const add3 = a => b => c =>
a + b + c
随着 ES6 箭头函数成为当今 JavaScript 开发人员的首选,我认为手动柯里化函数的选择是理所当然的。它实际上更短,并且以柯里化形式写出来的开销也更少。
也就是说,您仍然需要与不提供其公开的函数的柯里化形式的库进行交互。对于这种情况,我建议
-
curry
and curryN
(如上定义)
-
partial
(as 此处定义 https://stackoverflow.com/a/42164779/633183)
@Iven,
Your curryN
实施非常好。此部分专为您而存在。
const U = f=> f (f)
const Y = U (h=> f=> f(x=> h (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)
const add3 = curry ((x,y,z)=> x + y + z)
console .log (add3 (3) (6) (9))