9 ms·
Currying is not idiomatic in JavaScript
- tolmasky 9y agoSome of these points are true of currying in general -- IMO most of the time you can't tell what's a function call and what's a curry (Haskell people would say "its the same thing!"). However, separating these concepts leads to a lot of super interesting results. I wrote about this in this blog post: http://tolmasky.com/2016/03/24/generalizing-jsx/ http://tolmasky.com/2016/03/24/generalizing-jsx/ . I argue that the JSX syntax makes for an awesome syntax for currying. 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-jsx 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-ast http://runkit.com/tolmasky/generic-jsx-for-babel-javascript-...
- aidenn0 9y agoThanks for resharing that post; I found it more engrossing than the original article!
- deleted 9y ago[deleted]
- gizmo686 9y ago>Haskell people would say "its the same thing!" 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.
- lerie82 9y agoI think currying is hot...
- RA_Fisher 9y agoI'm not sure that idiomatic is a very rigorus idea when it comes to progamming languages. Anyone have good reads in that vein?
- sethrin 9y agoIt's not a rigorous idea, but I'm not sure what you might be wanting to read about, exactly. However, given that most of the language features that the article described are relatively new to the language, or are proposed language features, I think the author has a strong claim.
- sshine 9y agotl;dr 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-programmieren-JavaScript/dp/386899369X https://www.amazon.de/Das-Curry-Buch-Funktional-programmiere...
- emodendroket 9y agoDid I miss something? I see no mention of white space in this article.
- theoh 9y agoThe article says "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.
- 59nadir 9y ago> 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 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.
- 9y ago
- seniorsassycat 9y agoHas anyone read the semantics of the linked partial application proposal [0]? > 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#semantics https://github.com/rbuckton/proposal-partial-application#sem...
- megaman22 9y agoCurrying is one of those things I haven't grokked completely. And, shit, I just used grok as a past tense verb... I'm not sure if i just haven't drunk enough koolaid or if curriers are beyond the pale.
- emodendroket 9y agoIt's just a function returning another function. Like if you made a function that took x as its argument and returned a function that took y as its argument and added that to X and then you called it like add(2)(3). Or for a real world example check out $interpolate in angular 1.x.
- megaman22 9y agoI know all of these things... My brain just does not like the squishiness in most cases, or how silly and contrived most examples that I have seen are.
- emodendroket 9y agoWell they're contrived because a realistic example would have a ton of code having nothing to do with the concept
- megaman22 9y agoWell, in that case, they are of little more value than the equally contrived OOP examples we grew up with, with a base Fruit class, and Banana and Apple subclasses. Stupid examples are in many cases worse than no examples at all.
- emodendroket 9y agoWell I'm afraid I can't agree there. But like I said, the shower interpolate service is a real-world example and the purpose seems clear enough (why evaluate the string each time?)
- agentultra 9y agoIt can be idiomatic in Javascript if you choose a functional style. 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. [0] https://github.com/fantasyland/fantasy-land https://github.com/fantasyland/fantasy-land [1] https://github.com/sanctuary-js/sanctuary https://github.com/sanctuary-js/sanctuary
- gizmo686 9y ago>Most functional programming languages with automatic currying have syntax where there is no difference between add(1, 2) and add(1)(2). 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 add1
- biscarch 9y agoAll functions in Haskell are curried by default Your `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 https://www.haskell.org/hoogle/?hoogle=uncurry > uncurry converts a curried function to a function on pairs.
- tome 9y ago> All functions in Haskell are curried by default That's not really a meaningful statement. Functions are not really anything "by default".
- chriswarbo 9y agoCare to elaborate? I'd say functions in Haskell are many things "by default": - Curried - Partial - Lazy - Values - Recursive - etc.
- tome 9y agoLet's elaborate on "curried" since that was what OP was interested in. How are functions in Haskell "curried" by default? Sure, you can write 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 ?
- mattbierner 9y agoIn my experience, currying using helper libraries also makes debugging more difficult, as you have to step though higher order functions and cannot always easily tell how the argument values were calculated. This can be mitigated somewhat and the tools are improving but I generally found that, the more clean I made my JS code using functional programming concepts, the more difficult it was to debug when everything went wrong. And without a type system, I had to debug much more than in a lanague like scala
- mattbierner 9y agoThis opinion comes from my experience developing Khepri ( http://khepri-lang.com http://khepri-lang.com ), which tried to rework the JS syntax to better support untyped functional programming. Even had a partial application operator: @ 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
- taeric 9y agoIt is funny, because this is the argument against most use of macros in lisp. To the point that everyone knows that macros make it harder to debug code. 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.)
- linkmotif 9y agoWhat do you mean few people know how to step through programs nowadays? Is it uncommon to open up dev tools and step through code? I step through code whenever I get lazy or tired and don’t want to think and am using a platform with a nice debugger. I’d think this was very common but I live in a hole.
- always_good 9y agoI deal with stuff like http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode#map7 http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Jso... and I'm not sure how "auto"-currying is ever worth it when it comes at the expense of arity overloading and what else. I'd rather have to explicitly curry functions at the callsite with anonymous functions every time.
- erikpukinskis 9y ago“In JavaScript named parameters are simulated with object liberals” No. If you’re using object literals for required parameters you are doing it wrong. Maybe provide an options object, but otherwise if your function is so unwieldy as to require named parameters in JavaScript, refactor your function. If you want to curry a function but the curry target is in an options object, refactor your function. Or... horror of horrors write another wrapper function with a different signature. As for OP’s core argument: No. I use currying all the time in “idiomatic” JavaScript. The author is confused because most people who write JavaScript don’t write it idiomaticallly. ES6 and Babel have encouraged JS programmers to do whatever the f* they want to, but that’s their problem. I write plain, simple JavaScript, and currying is one of the (very few) powerful tools in my tool bag. If you are writing JS in a way that prevents currying you've thrown the baby out with the bath water. The whole point of idiomatic JavaScript is that you have closures, prototypes, currying, callbacks, and not much else. Code is understandable because you know it’s not using anything more than those simple control structures. You don’t use promises. You don’t use classes. You don’t use any declarative control structures at all. Anything else is bastard “modern” JavaScript, aka IO.js, aka Not JavaScript. If any of the words: “closure”, “prototype”, “currying” cause you some anxiety, I’m sorry you’re not a JavaScript programmer. You’re not “in” any real programming language at all, you’re using a bizarre Ruby subset on top of a JS VM without understanding what you’re doing. And you’re getting the worst of both worlds.
- chrisseaton 9y agoComments with one-word ‘no’ sentences are always the snobbiest, most patronising and most miserable comments.
- felixrieseberg 9y agoThe author is likely not confused, given that Axel turned "knowing JavaScript" into a fairly decent career. (Besides: Doesn't telling people that they aren't real programmers get really boring quickly? Shouldn't we all be over this by now?) [1] http://www.oreilly.com/pub/au/5474 http://www.oreilly.com/pub/au/5474
- linkmotif 9y ago
- h2j24 9y agoIs currying useful other than as a shorthand for "value object" creation? You can easily create the equivalent of a curried add function: 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.
- curun1r 9y agoEasier to compose with other objects, but harder to compose with other functions. For example: 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?
- h2j24 9y agoclass 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.
- SFjulie1 9y agooh the "I am a pedantic using and obscure feature thus I am a great dev"! Here comes back the variadic arguments from paleo C. This man hates people who will maintain his code and love bugs. 1) if you need an external library to reimplement an obscure feature introduced in original C, this is not idiotmatic. Idiomatic means in the core of the language 2) variadic arguments (varg vlist in C) are totally fine but this exemple totally misses the "idiomatic" use case of it and makes a very poor use of it. No this guy is not a great dev. If for only one thing his super exemple does an horrible thing: it makes the maintainer think it is one function while it actually branches in 2 functions. Functions should never change of behaviours with a same name. f(int, int) => int becomes f(int, int, ...) => f(int) => int which breaks any expectation of developers and static analysis tools.... Is their any good use case for this however? Yes, any cli tools intensively uses this in their main(), but devs like *BSD core devs will tell you they hate the bloat introduced by having too much useless branch points in this kind of function, specifically because it introducdes bugs that are hard to spot. Polymorphic function are also often called parsers ... and no devs love to see parsers everywhere.