Hacker News new | ask | show | jobs
by tommikaikkonen 3200 days ago
Currying in JavaScript is really nice if you can remember the arities of each curried function you're using. If you forget to call up to the last argument, you'll be passing a curried function instead of the expected value, and it can throw an error really far away from the bug. And instead of normal data to inspect at the error site to point you to the bad call, you'll only have a generic function name to look at. I've spent more hours than I'd like to admit debugging these situations.

A type system would detect these cases, but TypeScript and Flow don't have great support for curried functions. Typing them is very verbose.

4 comments

I think this exact problem is what makes currying in JS and dynamically typed languages not very practical.

In JS is even worse because of the weak typing, if by mistake you get a curried function instead of a value, JS will happily operate on it giving you things like "hellofunction () {}". When you realize that there is a problem, it would be hard to find the actual cause of the error.

I think this is the reason why dynamic functional languages such as Clojure don't use auto-currying. I a static typed world, like Haskell's this is not a problem at all.

Partial application on the other hand is very useful and practical in JS and other dynamic languages.

I beg to differ. Auto-currying (eg via Ramda) is awesome, powerful, and pragmatic.
The type of a curried function in Typescript is just something like:

  (a: number) => (b: number) => (c: number) => number
Sure, the parameter names and parentheses are a bit annoying, but I wouldn't call that "very verbose". Comparable concepts in C++ or Java would be a nightmare to type out.
I agree, that doesn't look bad. However, this type definition forces you to supply one argument per function call, which looks awful in JavaScript:

    fn(1)(2)(3)
That's a big drawback for me. Libraries like Ramda allow one or more arguments per function call:

    fn(1, 2, 3) === fn(1, 2)(3) === fn(1)(2, 3)
That's what makes the verbosity unbearable, as each type of call needs its own type.
At least in Flow it's actually possible to properly type curried functions!

Here's a gist of the type definitions I'm using: https://gist.github.com/noppa/c600cc43fd44e33768efe6c6eec4a9...

I think something similar might work in TS too.

Demo: https://goo.gl/w3aPsw

While Java’s option is quite painful

    Function<Number, Function<Number, Function<Number, Number>>>
The same in Kotlin is actually okayish:

    (Number) -> (Number) -> (Number) -> Number
I can't speak to Typescript but check out the Flow types for Ramda: https://github.com/flowtype/flow-typed/blob/master/definitio...

Maybe just a matter of perception but it looks verbose to me.

I've been using currying (via http://ramdajs.com/) in my projects for a while now and this is rarely a problem(yay for anecdotal evidence).

One reason for that is that my curried functions and their derivatives are usually named in a way that says exactly what they do.

Not only do you need to remember the arity, but the order as well, which is IMO worse (at least a type system can protect you from arity mistakes). If your function is not commutative for adjacent arguments of the same type, you're skipping through a minefield if you rely on currying.
How is that any different from having to remember the argument order in normal function application?
Conceptually it's not that different. Practically, it's pretty different because you're effectively fragmenting a single function application across your codebase, making it harder to reason about and more likely to make ordering mistakes.