Reputation:
Given is a simple, mathematically curried function to subtract numbers:
function sub(x) {
return function (y) {
return x - y;
};
};
sub(3)(2); // 1
The function signature reads exactly as the obtained result. The situation changes as soon as function composition is involved:
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
With function composition, the last parameter of each function involved is crucial, as it is fed respectively with the value returned by the previously applied function. The composition in the above example reads therefore as: 4 - 3 >= 2
which would yield false
. In fact, the computation behind the composition is: 2 >= 3 - 4
, which yields true
.
I can rewrite sub
and gte
easily to get the desired result:
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
But now the return values of directly called functions are different than expected:
sub(3)(2); // -1 (but reads like 1)
gte(2)(3); // true (but reads like false)
I could switch the arguments for each call or define a partial applied function for each case:
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
Both variants are obviously cumbersome and neither more readable.
Which parameter order is preferable? Or is there a mechanism to use both, depending on the respective requirements (like Haskell's left/right sections for partially applied operators)?
A possible solution must take into account, that I use unary functions only!
Upvotes: 1
Views: 364
Reputation: 74234
So you want to partially apply operators without having to write code like:
var gte2 = function (x) { return x >= 2; };
That's a reasonable use case, “to partially apply operators”.
The answer is simple. Just write a curried function. For example:
// var gte = y => x => x >= y; // ES6 syntax
var gte = function (y) {
return function (x) {
return x >= y;
};
};
var gte2 = gte(2);
Unfortunately, there are two ways we can partially apply binary operators:
This raises two important questions:
Fortunately, we can agree on one thing: it makes no sense to provide both arguments to the operator.
// Why write the following:
add(2)(3)
// When you can write the following:
2 + 3
The reason we are creating curried operator functions in the first place is for partial application.
Hence, it makes no sense to provide both arguments to the function “at once”.
What are the ramifications of this restriction? It means that:
We can choose any argument order we want.
// This is correct:
var sub = x => y => x - y;
// So is this:
var sub = y => x => x - y;
It only needs to make sense given one argument.
// 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.
Now, keeping this in mind which is the best argument order? Well, it depends.
For commutative operations the argument order doesn't matter.
For example, both addition and multiplication are commutative. Hence, a + b = b + a
for addition and a * b = b * a
for multiplication.
For non-commutative operations the right-to-left argument order is generally better because it allows the partial application to be read out loud.
For example, the expression lt(2)
generally means x => x < 2
and not x => 2 < x
.
Why is this the general case? Well, in JavaScript function names come before the argument. Hence, name(arg)
naturally reads as x => x name arg
instead of x => arg name x
.
However, there are some exceptions to the second rule. Most prominently, division:
div(10) // is read out loud as divide 10 by x
// it is not read out loud as divide x by 10
Of course, the correct argument order for such edge cases is a matter of debate but in my humble opinion the left-to-right argument order seems more natural.
So, here are a bunch of curried operator functions:
// 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;
Now, the second question is how do we partially apply the operators to “other” argument?
The only way to do so it to create a new function with the flipped argument order.
Fortunately, we don't need to create a new function for every operator:
For commutative operators the argument order doesn't matter. Hence:
flip(add) = add
flip(mul) = mul
For relational operators we don't need to create extra functions:
flip(lt) = gt
flip(gt) = lt
flip(lte) = gte
flip(gte) = lte
We only need to create flipped operator functions for sub
and 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)
All things considered, I would say that you should use your common sense.
Upvotes: 1
Reputation:
This response is based on the reply from Aadit.
There is actually a need for fully applied curried operator functions in Javascript - when used as First Class Citizens:
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
may be nonsense, but it serves as proof that fully applied curried operator functions make sense in Javascript. There are likely even other use cases.
Nevertheless Aadit is right that something like sub(2)(3)
contradicts the very purpose of currying!
So how could a solution look like?
Here is uncurryOp
:
// intended for all operator functions
function uncurryOp(f) {
return function (x, y) {
return f(y)(x);
};
}
uncurryOp(gt)(2, 4); // false (intuitive)
This is not really an adequate solution. I think there is no, due to lack of First Class and partially applicable operators in Javascript.
Upvotes: 0
Reputation: 324750
Here's how I read your composition:
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
So in short, it seems that your expectations are wrong. Maybe you do indeed have something backwards, but I honestly can't tell what. I do, however, think that this is not a good way to work in JavaScript and you're overcomplicating things, but that's just my opinion.
Upvotes: 0