CrowdHailer / fn.js

A JavaScript library built to encourage a functional programming style & strategy. - http://eliperelman.com/fn.js
MIT License
399 stars 30 forks source link

Make fn.flip only swap the first two arguments #13

Closed jpotterm closed 10 years ago

jpotterm commented 10 years ago

Currently, flip will reverse all the arguments. This is a problem when working with functions that take optional arguments at the end. For example, say you have:

var echo = function(a, b, includeNewline) {
    if (includeNewline) {
        return [a, b, "\n"];
    } else {
        return [a, b];
    }
};
var flipEcho = fn.flip(echo);

where includeNewline is an optional argument. You probably flipped it so you could partially apply b:

var partialEcho = fn.partial(flipEcho, 1);

The problem is that the argument you just partially applied is either b or includeNewline depending how many arguments you call it with:

partialEcho(2); // a = 2, b = 1
partialEcho(2, true); // a = true, b = 2, includeNewline = 1

and this was not the intention.

I think flip only really makes sense for the first two arguments anyway, and the use case is nearly always to partially apply the second argument. So this pull request makes flip only swap the first two arguments and leave the others in the same order.

eliperelman commented 10 years ago

Instead of making this change specific to only 2 arguments, what if the fn.flip by default flips the same number of arguments as the arity of the handler, otherwise a specified number or arguments?

jpotterm commented 10 years ago

I think making flip specific to 2 arguments is a good thing. That's the way it is defined in every functional language I've ever seen (if you know of an exception, let me know). Flip should be used sparingly anyway, but I think using it on more than 2 arguments would make your code unreadable very quickly. It's main use case (in my opinion) is to curry the second parameter of a binary operator.

If you wanted to curry the fourth parameter instead, I'd expect the clearest way to do that would be a function that moved the fourth parameter the front and left the other parameters in the same order. Something like:

flipN(4, function(a, b, c, d) {}); // function(d, a, b, c)

The only case I can think of where reversing the arguments would be at all useful is if you wanted to curry the second and third arguments but not the first. And even in that case I think your code would be much more readable if you defined a wrapper function.

So in summary, I don't think flip is a very useful function to generalize, but if we were going to generalize it, I think a function that moved the nth argument to the front would be much more useful than reversing the arguments.

Do you have a use case in mind for reversing the arguments?

eliperelman commented 10 years ago

I think the semantics for having a function named flip assumes that you are going to be flipping the arguments of a given function, and that means all arguments. If we need a function to do specific reordering of a function's arguments, I think a new method should be introduced.

jpotterm commented 10 years ago

Well from what I've seen, flip functions from other languages are only defined for 2 arguments. So I don't think it's clear that the extrapolation to n arguments is reversal.

The word itself might imply reversal, but I think that's because it was only ever meant to apply to two arguments. So I guess I'm saying that if we are trying to keep what people are used to, then it should only flip the first 2 arguments. If we're trying to generalize it to n arguments (which people won't expect), the only useful way (IMO) seems to be moving the nth argument to the front.

eliperelman commented 10 years ago

Yes, fn.flip's semantics are going to be defined as a general way to reverse the order or arguments of a given function. That may be bucking a trend, but I see it as a good thing. Like I said, if we need to have another method for re-ordering arguments, I'm all for it.

jpotterm commented 10 years ago

Ok, do you think I should submit this functionality under a function with a different name (e.g. fn.swapArgs)? I feel like both functions would have extremely similar use cases, and I don't want to clutter the API.

Just so you know, the reason this came up for me was that I was trying to flip a binary operator and pass it to filter:

var eq2 = fn.partial(fn.flip(fn.op['===']), 2);
fn.filter(eq2, [1, 2, 3, 4]); // Expected: [2], actual: []