Currying is not idiomatic in JavaScript
2ality.com
2ality.com
Edit: Some follow up articles:
1. Using this scheme, you can actually have default parameters in currying: https://runkit.com/tolmasky/default-parameters-with-generic-...
2. An example of how to apply this to Babel syntax trees: http://runkit.com/tolmasky/generic-jsx-for-babel-javascript-...
Haskell can get away with this because it is a pure language. If you have `f a b`, you do not really care when f gets evaluated (or, at least, when you do care currying is generally not what makes figuring it out difficult). Even without currying, Haskell makes it difficult to know when a function is evaluated.
I think Javascript has become a kitchen-sink language. It is, as you say, a language that affords many styles. If you pick a sub-set of the language that is functional you can see that currying is quite idiomatic in that context.
That being said I think library support is a necessity when choosing a more functional approach in Javascript as the "core" experience in JS caters to an imperative, C-derived language (as per many of the examples). If you work with Ramda you can get most of what you need today to make working with currying, and it's compositional capabilities, pleasant.
Consider R.curryN:
const add = R.curryN(2, (a, b) => a + b)
You can now call this: add(1)(2)
add(1)
add(1, 2)
And you have options in terms of order of parameters: const sillyAdd2 = add(R.__, 2) // a silly example
sillyAdd2(3) // => 5
And if you want even more flexibility there is Fantasyland[0] and Sanctuary[1] among others.Javascript is fertile ground, in my experience, for getting developers to experiment with and adopt functional programming paradigms.
And the language’s implementation was beautiful: declarative, no mutation, and HOFs everywhere. It even used monad transformers. But the nightmare that was debugging also convinced me that I had actually been trying to solve the completely wrong problem
The lost part, is few people actually "debug" in the "interactive debugger" sense of the word anymore, such that many things that were frowned upon in lisp and related languages are now getting a lot of exposure. And I feel it is truly sad how few people know how to step through a program nowdays. (Obviously projecting some. Maybe I'm in an odd corner where nobody uses an interactive debugger, but it has been a long time since I found coworkers that were used to using one. And, in this, I'm including REPL based workflows. Yes, I know they are strictly different. I just feel they are in the same family for this discussion.)
I think this is an odd one. That the act of stepping through code hella build the skill to mentally simulate stepping through code. Which helps reason about it. I don't have data to back that claim. :(
But it's also a change in coding conceptualisation. Personally I understand things from the ground up, from composition of very low level components. I find it hard to reason about a system's performance and failure modes without doing so. But increasingly developers have only a surface area knowledge about the things they use, and instead know a lot in terms of breadth instead of depth. This approach is more effective for building something quickly by duct taping disparate things together, and this is the majority of modern commercial coding. These people don't need debuggers: it gives them too much information. They need examples, and they code by idiom and analogy instead.
Things like event dispatchers and async also greatly diminish the power of the debugger.
That said, my main point is that where many folks used to advise caution, such that instead of promoting lisp, they would create new languages where they could hide some of the magic, it seems that people are gung-ho on many of the higher abstractions now, regardless of the mental costs they bring.
> Note that this also means that more involved references are captured in their entirety and should be stored in a local variable if they may have unintended side-effects should the partially applied function result be called more than once
const a = [{ c: x => x + 1 }, { c: x => x + 2 }];
let b = 0;
const g = a[b++].c(?);
b; // 0
g(1); // 2
g(1); // 3
b; // 2
`a[b++].c` is not evaluated until the partially applied function `g` is called. Event `a[b++]` and `b++` are not evaluated.It is unclear to me when `const g = Math.random().toFixed(?)` would evaluate `Math.random()`.
[0]: https://github.com/rbuckton/proposal-partial-application#sem...
Currying is nice when whitespace is function application and ugly and inconvenient when you have to write foo(x)(y) and the libraries you deal with aren't consistent about this. Currying in Haskell is nice for exactly these reasons.
Also, https://www.amazon.de/Das-Curry-Buch-Funktional-programmiere...
"Most functional programming languages with automatic currying have syntax where there is no difference between add(1, 2) and add(1)(2)"
and goes on to talk about unidiomatic syntax.
In Haskell, the two options are "add (1,2)" and "add 1 2" The latter, curried form, involving whitespace, works because Haskell parses it as an application of a to 1, followed by an application of the resulting value to 2. This syntax is inspired by the lambda calculus, so it's not really whitespace that's the concept, just "juxtaposition of terms" implies application.
This is a bit of a misrepresentation. There is nothing "uncurried" about using these parens here. It's simply making a function call on two parameters take a tuple of two values instead. There's no point to it at all and there's no real value in doing it in your APIs. I have no idea why the Haskell wiki insists on framing it like that.
Using the "curried form" has no effect on how the function call will perform or behave at all. Partially applying functions can have performance implications, though.
This isn't true of any functional programming language I can think of.
What happens in most functional programming languages is that it is idiomatic to make functions curried by defualt, and only make non-curried functions when you have reason to.
For example, in Haskell, you could have either:
add1 :: Int -> Int -> Int
add1 x y = x+y
add2 :: (Int, Int) -> Int
add2(x,y)=x+y
which would be called as: add1 2 3 --equivalent to (add1 2) 3
add2(2,3)
There is even functions to convert between these: add1 = uncurry add2
add2 = curry add1Your `add2` uses a tuple instead of currying whereas `add1` is already curried. In ghci:
Prelude> (+) 3 4 == (+ 3) 4
True
So you could have an `add3` just by applying 3 as the first argument. Prelude> let add3 = (+ 3)
Prelude> add3 5
8
hoogle describes uncurry as a function on pairs. https://www.haskell.org/hoogle/?hoogle=uncurry> uncurry converts a curried function to a function on pairs.
That's not really a meaningful statement. Functions are not really anything "by default".
- Curried
- Partial
- Lazy
- Values
- Recursive
- etc.
f x y = 2 * x + y
and that will define a function that takes an Int, say, returns a function. But why is that any more "default" than g (x, y) = 2 * x + y
? add x y = x + y
it's sugar for multiple single-argument functions. add = \x -> \y -> x + y
When examining it this way, the `g` function above would translate to g = \(x, y) -> 2 * x + y
Which is a function taking a single tuple argument. It is still "default curried" but the argument being passed in is a single argument rather than multiple so we don't expand it to multiple single-arg functions. Perhaps it is more illustrative to show the definition as the single argument it is rather than using haskell's destructuring to pull x and y out of the tuple. g tuple = 2 * (fst tuple) + (snd tuple)
and a ghci session for completeness: Prelude> let g (x, y) = 2 * x + y
Prelude> g (1,2)
4
Prelude> let y tuple = 2 * (fst tuple) + (snd tuple)
Prelude> y (1,2)
4
So when we say that Haskell functions are "curried by default", what we're referring to is roughly the underlying single-argument nature of haskell functions. function g(args) {
return 2 * args[0] + args[1];
}
You're right that we could write either `f` or `g`, but the reason `f` is the "default" way is that it involves no extra datatypes. Consider the following: h [x, y] = 2 * x + y
I wouldn't say this is "default behaviour" in the same way as `f` or `g`, but it certainly seems 'closer' to `g`. Similarly: data Pair a b = MkPair a b
i (MkPair x y) = 2 * x + y
This seems very "non-default", especially since it's using a non-default datatype. Yet that datatype is alpha-equivalent to `(x, y)`.Whilst tuples (or the above `Pair`) are isomorphic to curried functions (e.g. via the `uncurry` function and its inverse), they're not alpha-equivalent, so there is a meaningful distinction between tuples and curried functions. Since tuples are defined in the Prelude, we could do away with them (and use `MkPair` instead, if we wanted), so they're not really "built in". On the other hand, functions are (AFAIK) built quite strongly into the core of Haskell, and hence we cannot avoid them, making them, and hence the function-returning behaviour of currying, more pervasive, unavoidable and hence "default".
(Note that Haskell may at some point be less tied to functions; e.g. Conal Elliot's 'compiling to categories', among others, looks like a reasonable justification for allowing something like an "OverloadedFunctions" pragma)
I'd rather have to explicitly curry functions at the callsite with anonymous functions every time.
class Adder {
base: number;
constructor(base: number) { this.base = base; }
add(o: number): number { return this.base + o; }
}
While slightly more verbose, I believe this form to be the more powerful of the two because one has access to all of the features of imperative programming and is easier to compose with other objects. const compose = (...f) => f.reduceRight((f, g) => (...x) => f(g(...x)));
const add = x => y => y + x;
const mul = x => y => y * x;
const div = x => y => y / x;
// (6x + 4) / 2
const complexOp = compose(mul(6), add(4), div(2));
// should return 50
complexOp(16);
How would you accomplish this using classes? class Answer {
getAnswer(input: number) { return (input * 6 + 4) / 2; }
}
All joking aside, you would achieve composition the same way, except using interfaces. interface Operation<T> {
apply(input: T);
}
class Addition implements Operation<Number> {
}
class Multiplication implements Operation<Number> {
}
class Division implements Operation<Number> {
}
function compose(Operation<T>... operation, T input) {
T last = input;
for (Operation<T> op : operations) {
last = op.apply(last);
}
return last;
}
Of course, the above code is slightly verbose, but even if you could remove all the boiler plate it still doesn't look like good imperative code. This is what confuses me about currying: when translated to the imperative equivalent, it looks terrible.I'm not sure if i just haven't drunk enough koolaid or if curriers are beyond the pale.
Stupid examples are in many cases worse than no examples at all.
I have an array of objects in my state. For this example we'll say they are Todos, and each one has three properties, id, name, and checked. I have a function `checkTodo` with the signature `checkTodo(id: string, event: InputEvent)` that I want to call if one of those Todos is checked.
So say I'm using React. I can use arrow functions like this:
todos.map((todo) =>
<input type="checkbox" onChange={ (event) => checkTodo(todo.id, event) } checked={ todo.checked } />
<label>{ todo.name }
)
That's okay, but having arrow functions like that in JSX (which you'll have several of in a complex application) can get a bit unwieldy. Currying gives us another option that can be cleaner and avoids having to declare functions inside our render. If we change our method signature to this: `checkTodo = (id: string) => (event: InputEvent)` then we can write our JSX like this: todos.map((todo) =>
<input type="checkbox" onChange={ checkTodo(todo.id) } checked={ todo.checked } /><label>{ todo.name }
)
It's a bit cleaner, and can make certain patterns a lot easier in the long run. For example, say we make a shared <Todo /> component like the one above which can be rendered both in a list, as above, or on it's own. In that case, it would be bad for the method signature that the Todo component expects to be the id and event, as the id wouldn't be necessary when we're not working with an array.Instead, currying means our Todo component can simply expect a function that takes in an InputEvent, and currying can move the other logic where it's needed.
Does that make sense? If you don't know React I could rewrite this using native DOM calls.
edit: a word
Now you can think of an array as a restricted function whose domain is 0..n x ... and range is some type. The same concept of "currying" applies to functions. That is, given a function of N arguments, if you provide K (less than N) arguments you get another function of N-K arguments.
It is strange that many programming languages allow currying of arrays but not of functions!
The closest real world example I can think of right now is calling a webservice. Which is usually something like
const baseUrl = 'https://some.tld'
function callExternal(resourcePath, data) {
fetch(baseUrl + resourcePath, data)...
}
// and then somewhere
callExternal('/some/path', {some: data})
With currying you could do it like this: const baseFun = function(baseUrl, resourcePath, data) {
fetch(baseUrl + resourcePath, data)...
}
const callExternal = baseFun('https://some.tld')
// ^ you "pin" the first argument to always be 'https://some.tld'
// and then somewhere
callExternal('/some/path', {some: data})
I personally have had the need to write a curried function myself maybe twice over the course of the past 17 years.Of course, one can wonder how many times can one usefully curry with external functions, but I think that's partially a result of not having currying syntax in the first place. For example, the fact that Ruby has blocks massively influences the typical design of its libraries, when compared to Python.