给出一个简单的数学曲线函数来减去数字:
function sub(x) {
return function (y) {
return x - y;
};
};
sub(3)(2); // 1
函数签名与获得的结果完全相同。一旦涉及职能构成,情况就会发生变化:
function comp(f) {
return function (g) {
return function (x) {
return f(g(x));
};
};
};
function gte(x) {
return function (y) {
return x >= y;
};
};
comp(gte(2))(sub(3)) (4); // true
对于函数组合,所涉及的每个函数的最后一个参数是至关重要的,因为它分别用先前应用的函数返回的值来馈送。上例中的组成因此读作:4 - 3 >= 2
,它将产生false
。实际上,合成背后的计算是:2 >= 3 - 4
,它产生true
。
我可以轻松地重写sub
和gte
以获得所需的结果:
function sub(y) {
return function (x) {
return x - y;
};
};
function gte(y) {
return function (x) {
return x >= y;
};
};
comp(gte(2))(sub(3)) (4); // false
但是现在直接调用函数的返回值与预期不同:
sub(3)(2); // -1 (but reads like 1)
gte(2)(3); // true (but reads like false)
我可以为每个调用切换参数或为每种情况定义部分应用函数:
function flip(f) {
return function (x) {
return function (y) {
return f(y)(x);
};
};
}
flip(gte)(2)(3); // false
var gteFlipped = flip(gte);
gteFlipped(2)(3); // false
这两种变体显然都很麻烦,而且不易读取。
哪个参数顺序更合适?或者是否存在使用两者的机制,具体取决于各自的要求(如Haskell的左/右部分用于部分应用的运算符)?
一个可能的解决方案必须考虑到,我只使用一元函数!
答案 0 :(得分:0)
以下是我阅读你作文的方式:
comp(gte(2))(sub(3)) (4);
gte(2) = function(y) { return 2 >= y; } // (x = 2)
sub(3) = function(y) { return 3 - y; } // (x = 3)
// Therefore:
comp(gte(2))(sub(3)) = function(x) {
var f = function(y) { return 2 >= y; };
var g = function(y) { return 3 - y; };
return f(g(x));
};
// Now call with (x = 4):
x = 4
g(4) = 3 - 4 = -1
f(-1) = (2 >= -1) = true
简而言之,似乎你的期望是错误的。也许你确实有倒退的东西,但老实说我说不清楚。但是,我认为这不是一种在JavaScript中工作的好方法,而且你过于复杂,但这只是我的看法。
答案 1 :(得分:0)
因此,您希望部分应用运算符而无需编写如下代码:
var gte2 = function (x) { return x >= 2; };
这是一个合理的用例,“部分应用运营商”。
答案很简单。只写一个curried函数。例如:
// var gte = y => x => x >= y; // ES6 syntax
var gte = function (y) {
return function (x) {
return x >= y;
};
};
var gte2 = gte(2);
不幸的是,我们可以通过两种方式部分应用二元运算符:
这提出了两个重要问题:
幸运的是,我们可以就一件事达成一致:向运营商提供两个参数是没有意义的。
// Why write the following:
add(2)(3)
// When you can write the following:
2 + 3
我们首先创建curried操作符函数的原因是部分应用。
因此,同时为函数“提供两个参数是没有意义的。
这种限制的后果是什么?这意味着:
我们可以选择任何我们想要的参数顺序。
// This is correct:
var sub = x => y => x - y;
// So is this:
var sub = y => x => x - y;
只有一个论点才需要有意义。
// Consider this operator function:
var sub = y => x => x - y;
// This makes sense:
sub(1) // means (x => x - 1)
// However, this doesn't make sense:
sub(2)(3) // expected (2 - 3) but actually (3 - 2)
// But that's OK because it only needs to make sense given one argument.
现在,记住这是最好的参数顺序?嗯,这取决于。
对于commutative操作,参数顺序并不重要。
例如,加法和乘法都是可交换的。因此,a + b = b + a
用于加法,a * b = b * a
用于乘法。
对于非交换操作,从右到左的参数顺序通常更好,因为它允许大声读出部分应用程序。
例如,表达式lt(2)
通常表示x => x < 2
而非x => 2 < x
。
为什么这是一般情况?好吧,在JavaScript函数名称出现在参数之前。因此,name(arg)
自然为x => x name arg
而不是x => arg name x
。
但是,第二条规则有一些例外。最突出的是,分裂:
div(10) // is read out loud as divide 10 by x
// it is not read out loud as divide x by 10
当然,这种边缘情况的正确论证顺序是一个争论的问题,但在我看来,从左到右的论证顺序似乎更自然。
所以,这里有一堆curried运算符函数:
// Commutative operators:
var add = x => y => x + y;
var mul = x => y => x * y;
// Right-to-left operators:
var lt = y => x => x < y;
var gt = y => x => x > y;
var lte = y => x => x <= y;
var gte = y => x => x >= y;
var sub = y => x => x - y;
// Left-to-right operators:
var div = x => y => x / y;
现在,第二个问题是我们如何将运算符部分应用于“其他”参数?
唯一的方法是使用翻转的参数顺序创建一个新函数。
幸运的是,我们不需要为每个运营商创建一个新功能:
对于交换运算符,参数顺序并不重要。因此:
flip(add) = add
flip(mul) = mul
对于关系运营商,我们不需要创建额外的功能:
flip(lt) = gt
flip(gt) = lt
flip(lte) = gte
flip(gte) = lte
我们只需要为sub
和div
创建翻转的运算符函数:
var subFrom = x => y => x - y; // subFrom(5) means (y => 5 - y)
var divBy = y => x => x / y; // divBy(10) means (x => x / 10)
考虑到所有事情,我会说你应该运用常识。
答案 2 :(得分:0)
此回复基于Aadit的回复。
实际上需要在Javascript中完全应用curried操作符函数 - 当用作头等公民时:
function between(ops) {
return function (left) {
return function (right) {
return function (n) {
// At this point one should use the native Javascript operators
// but they can't be passed to the function, since operators are not First Class.
return ops[0](left)(n) && ops[1](right)(n);
};
};
};
}
function lte(y) { return function (x) { return x <= y; }; }
function gt(y) { return function (x) { return x > y; }; }
between([gt, lte])(2)(4)(4); // true
// is evaluated as: gt(2)(4) && lte(4)(4) === true; (confusing)
between
可能是无意义的,但它可以证明完全应用curried运算符函数在Javascript中是有意义的。甚至可能还有其他用例。
然而,Aadit是正确的,像sub(2)(3)
之类的东西与currying的目的相矛盾!
那么解决方案怎么样?
以下是uncurryOp
:
// intended for all operator functions
function uncurryOp(f) {
return function (x, y) {
return f(y)(x);
};
}
uncurryOp(gt)(2, 4); // false (intuitive)
这不是一个真正适合的解决方案。我认为没有,因为在Javascript中缺少First Class和部分适用的运算符。