The (pre)fix is in(fix)

***UPDATE***

Turns out this didn't work well! It was both confusing to users and inconsistent in its behavior. Read the next post "It turns out that JavaScript is not Haskell to see what we're doing about it.

For Ramda we’ve wrapped several JavaScript infix operators as functions, i.e. in prefix form. You gotta do that if you want to curry and compose with those operations. Some of the time, that works out fine. add, for example, converts to prefix form with no confusion:

    R.add(10, 3) // 10 + 3
    var add10 = R.add(10);
    add10(3); // 13, duh
    

add converts to infix easily because it is commutative: a + b == b + a. Infix operators that are not commutative convert awkwardly. Consider divide:

    R.divide(10, 2) // 10 / 2
    var divide10 = R.divide(10); // uh ... wait a second ...
    divide10(2); // 5 ... something smells funny ...
    

Here the order of arguments matters, and the semantics start to wobble. add(10) clearly means “If you give me a number, I will add 10 to it.” divide(10) on the other hand, seems to be saying “If you give me a number, I will divide it by 10.” But that is not what it means. What divide(10) means is “if you give me a number, I will divide that number into 10.”

The partially applied version of divide is also probably not very useful. How often do you store a number so you can divide it by a bunch of other numbers? I’d guess not very frequently. It’s typically more useful to curry in the divisor of this operation, not the dividend.

We noticied this peculiar property of the non-commutative operators converted to prefix functions pretty early on. It always rankled us, but we didn’t have a good solution for it. To get around it, we defined functions like divideBy. divideBy was a flipped version of divide; so when you partially applied divideBy it would read more like what the behavior is:

    R.divideBy(2, 10) // 10 / 2
    var divideBy2 = R.divideBy(2);
    divideBy2(10); // 5, duh
    

… but that always felt like a hack. Why should this one operation take two functions to express?

This behavior was especially obnoxious with curried relational operators, viz. <, <=, >, >=. (Equality operators are commutative, so those were cool.)

For example, consider < (less than) converted to the prefix function lt:

    R.lt(2, 10) // 2 < 10
    var lt2 = R.lt(2); // oh no
    lt2(10); // true. wtf?
    

We couldn’t hack around these by flipping them and giving them separate names, because the flipped versions have the opposite meaning, e.g. R.flip(R.lt) == R.gte. The cure is worse than the disease.

Scott posted this problem as a desparate plea on Stack Exchange, and got a spectacular answer and solution. Not surprisingly, the insight comes from Haskell. I recommend you read the response by Aadit M. Shah, it’s well worth a read. Go ahead, I’ll wait.

The essence of the solution is to treat infix operators differently. Instead of currying them from left to right, they are curried in the following manner:

This approach threads the needle of converting from infix to prefix while maintaining (improving) readability. An added bonus: we can get rid of all the flipped/renamed functions, so the API footprint is a little smaller. Win-win-win.

This distinction in the way we are treating infix operators is coming up in ramda v0.5.0. We will also be exposing an op function on ramda, so you can make your infix-style functions behave this way, should you so desire.

Buzz de Cafe 14 September 2014