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:
add
converts to infix easily because it is commutative: a + b == b + a
.
Infix operators that are not commutative convert awkwardly. Consider divide
:
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:
… 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
:
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:
lt(10)
returns a function like x -> x < 10
undefined
, then it is applied left, i.e
lt(10, undefined) :: x -> 10 < x
.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.