6 ms·
Currying in JavaScript
- citizenofpluto 11y agoclicked on the link hoping it would explain why I might need to use currying.
- kevincennis 11y agoYeah, sorry about that. I realized afterwards that a better title would probably have been "Writing a Curry Function in JavaScript".
- bpicolo 11y agoOne example is it would allow you to pass arguments to callbacks other than those passed by whoever is using the callback (my example because it's the relevant part I use most frequently) There are infinite examples for this sort of thing in real development scenarioes
- Zalastax 11y agoIn what way is it better than partial() from rambda or underscore? I have tried to see the potential in currying but gave up when I couldn't create a function with no parameters without calling it.
- amalcon 11y agoIt isn't better; it's the same thing less a few minor details. _.partial is a from of currying. It's just a matter of which syntax you prefer.
- bpicolo 11y agoWhy did you think you needed it in the case where you had a function that didn't need parameters? That sort of relinquishes the point of currying. (Should just be passing the function itself)
- Zalastax 11y agoI had a function taking parameters and wanted a function without parameters. Using this style the function would be called immediately when all parameters are supplied, instead of in the callback as I intended.
- bpicolo 11y agoAhh, I see what you meant. That should work with a correct currying implementation for sure
- braythwayt 11y agoIt’s often the case that writing a bit of infrastructure for something is a valuable exercise in and of itself. So we can debate how often you’d need to curry a function, but I feel safe suggesting that writing your own curry function (or reading along with an essay that does the same with lively interest) is valuable. Same with things like classes and mixins, there is now syntactic sugar for such things, but it’s always a good thing to have written your own MakeClass function at least once.
- kevincennis 11y agoOP here. That was kind of my intent (learning exercise rather than super practical real-world technique) -- but I'll admit I probably could have picked a better title.
- braythwayt 11y agoWell, I liked it, and I personally don’t mind the title. It’s about currying and JavaScript, and it’s interesting, so that meets my (possibly low) bar for upboating.
- hvs 11y agoRamdajs links to an old article that explains it pretty well: https://web.archive.org/web/20140714014530/http://hughfdjackson.com/javascript/why-curry-helps https://web.archive.org/web/20140714014530/http://hughfdjack... Ramdajs: http://ramdajs.com/0.16/index.html http://ramdajs.com/0.16/index.html
- nothrabannosir 11y agoCareful: objects.map(get('id')) and poof there goes your type safety. Optional typing (flow and TS) is making inroads into JS. If you want to bet on them (or any other JS inspecting tools), make sure you use this: objects.map(function (x) { return x.id; }); Or, in ES6 or TypeScript: objects.map(x => x.id) This helps computers understand your code.
- chrisseaton 11y agoCan these typing algorithms not use the same transformation themselves to make sense of a call to get? I know there's a string there but it's literal.
- nothrabannosir 11y agoAbsolutely! If enough people do this, the tooling might eventually adapt. However, it's the kind of bet I wouldn't go long on. Today, TS automatically (and correctly) infers the type of objs.map(x => x.foo). I suppose the same holds true for flow. This is not hypothetical; try this in http://www.typescriptlang.org/Playground http://www.typescriptlang.org/Playground var objects = [ { id: 1, name: "Foo" }, { id: 2, name: "Bar" } ]; var look_at_my_type = objects.map(x => x.id); Note; this is 100% JS, just passed through a TS compiler. Check the type of look_at_my_type (e.g. start typing its name at the bottom, see what the popover says): number[]. Replace it by get('id') (with definition function get(name: string) { return x => x[name]; }), and the type becomes any[].
- jamesfe 11y agoI guess I'd be more interested in how this solution came to be, rather than a step by step of how to get there. What other methods did you try? What worked and what didn't? How did you solve the problems you came across? That being said - well written, very informative, and I very much appreciated the simple and concise definition of currying. Thanks.
- kevincennis 11y agoHey. Thanks so much for the feedback. It's been a while since the first time I ever tried to do this, so it's a bit difficult to remember exactly what my initial approach was. But I definitely remember the moment where I realized I needed the `resolver` function. Initially, I think I tried to just make `curry` return something that looked like the anonymous function inside of `resolver` – which of course didn't work, because I had no way to store all of the previous arguments. Ultimately, I think there are probably a number of good ways to do this, and I would never claim that mine is the "right" one. It's really just a representation of the way I think about things, I guess. If you do a bit of digging, there are some other great blog posts about currying, and each one has a different approach. It's actually pretty interesting to see how different people have implemented it. For a what basically amounts to a 10-line function, there's a pretty amazing amount of diversity in the way people approach it.
- twic 11y agoWhy does this need the immediately invoked function called 'resolver'? Immediately invoked functions are usually about establishing a clean namespace, aren't they? What's this one being used for here? Also (possibly related), what does 'arguments' in "Array.prototype.slice.call( arguments )" resolve to, and when? I would have thought it would be evaluated when 'resolver' in invoked, and would therefore evaluate to an empty array. If so, isn't there a simpler way to make an empty array?
- kevincennis 11y agoBoth good questions. The immediately invoked function is really just a slightly less verbose way to do this: function resolver() { } return resolver(); The reason that I gave it a name instead of using an anonymous function (as is common with immediately invoked function expressions) is because I need to be able to call it from within itself. This function is all about creating a closure where we can store all of the arguments we've received so far. As for your second point: The first time that `resolver` is called, arguments is in fact empty. But if you read a bit further, you'll see that `resolver` calls itself recursively (indirectly, via the anonymous function it returns), and does in fact pass arguments. So you can't just start off with an empty array each time. You need to make sure you're taking into account any arguments that were passed in. Does that make sense?
- amelius 11y agoIn my opinion, this functionality should be part of the new javascript standard.
- kevincennis 11y agoYou can get a lot of value out of `Function#bind()`, which has been around since ES5. That doesn't let you curry functions, but it does give you partial application, which I think is actually more useful on a day-to-day basis. If you're not familiar, here's a very quick and admittedly somewhat contrived example: function clamp( lo, hi, val ) { return Math.max( lo, Math.min( hi, val ) ); } var tenToTwenty = clamp.bind( null, 10, 20 ); tenToTwenty( 5 ); // 10 tenToTwenty( 15 ); // 15 tenToTwenty( 25 ); // 20 For what it's worth though, I agree that having Function#curry in the ES spec would be pretty cool.
- satori99 11y agoI recently became aware that functions returned from bind perform realtively poorly in modern browsers. I am not sure of exactly why, but a simple closure outperforms the functions returned from bind() significantly in all the browsers i have ever tested in. If performance matters native bind() can be a pain.
- kevincennis 11y agoIt's all relative, right? If I'm writing some super perf-sensitive code like manipulating an array of PCM data for Web Audio stuff or doing a complex canvas animation, then the savings of not using Function#bind are probably kind of important. For most everyday code, avoiding bind() kind of feels like premature optimization to me.
- satori99 11y agoYeah, definately a realtive issue. And also easy to fix after the fact, but as you say, it becomes important for inner-loop stuff. I spent a few hours removing it from a codebase of mine last week, to see substantial improvements on all browsers.
- tel 11y agoCurrying on the fly always feels a little silly to me. What's natural is just defining functions in curried form from the get-go. function reduce(nil, cons) { return (ary) => { ... } }
- ufo 11y agoI didn't have a great experience with currying in Javascript when I tried to use it. In Haskell passing the wrong number of arguments to a function results in an immediate type error even when currying while in Javascript the error is going to end up being shallowed by the currying. You will only notice the problem a lot later, when you notice that something is a partially evaluated function when it shouldn't. Another problem with currying in Javascript is that it leads to more complicated stack traces, which is unfortunate for debugging.
- zeckalpha 11y agoPerhaps this method could be used by a strongly-typed compile-to-js curried language. (PureScript, Elm, and friends)
- skwosh 11y agoI haven't used either but since they're based on Haskell I would assume that currying is inherent to the language (functions are curried by default).
- zeckalpha 11y agoHence a "curried language".
- tel 11y agoYeah, but a technique like this just isn't useful there. The host language handles when function application occurs.
- zeckalpha 11y agoNo, curried languages compiled output typically has tricks like in the OP and they typically rely on a run time system to help with the 'impedance mismatch' between curried and not languages.
- 11y ago
- luckydata 11y agoWhy is this useful?
- wizao 11y agohttps://www.youtube.com/watch?v=m3svKOdZijA https://www.youtube.com/watch?v=m3svKOdZijA
- couchand 11y agoWe can tell how many arguments a function expects by accessing its length property. While that works for many cases it isn't generally true. For instance: function wtf() { return arguments[0]; } While that may seem like a ridiculous example, the following idiom is quite common in JS programming: function accessor(value) { if (typeof value === 'undefined') { return getValue(); } else { setValue(value): } } In general it's not a good idea to expect any particular value for a function's length property.
- kevincennis 11y agoRight, but you wouldn't curry those functions.
- ufmace 11y agoIt's a neat solution and a good demo of the cool stuff you can do with Javascript. I kinda doubt the practicality, though. If I wanted to do something like that in production, I think I'd rather just define a new function right there, since you could curry any argument order you wanted instead of only in order, and you could name everything more clearly and have the code that you're running right there instead of in a far-away library. Like if you had to call a method like this: sendMessage(sender, receiver, data); a bunch of times with the same receiver but different senders, you could define: function sendMessageToBob(sender, data) { sendMessage(sender, bob, data); }
- deleted 11y ago[deleted]
- tobyhinloopen 11y agoFor every use case this might be applied, .bind -a built-in function- is superior.
- josteink 11y agoI actually had to look this up, because I didn't know about it, and yes I agree. MDN documentation contains some very illustrative examples of its use: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- Nemcue 11y agoWhat about a function that you want to pass around and have other actors give it arguments until it executes? Can't really do that with .bind without executing it.
- serve_yay 11y agoES6 rest/spread operator is really nice for dealing with function arguments. function curry(fn) { return function accum(...args) { if(args.length >= fn.length) { return fn.apply(null, args) } return accum.bind(null, ...args) } }
- tomkwok 11y agoA real-world use case of currying in JavaScript: jqMath [1] from mathscribe.com, a lightweight alternative to jsMath (which is succeeded by MathJax). jqMath utilizes its own jsCurry library [2], and both are released under MIT license. [1]: http://www.mathscribe.com/author/jqmath.html http://www.mathscribe.com/author/jqmath.html http://www.mathscribe.com/mathscribe/jqmath-0.4.3.js http://www.mathscribe.com/mathscribe/jqmath-0.4.3.js [2]: http://www.mathscribe.com/mathscribe/jscurry-0.4.0.js http://www.mathscribe.com/mathscribe/jscurry-0.4.0.js http://www.mathscribe.com/mathscribe/jscurry-documentation.txt http://www.mathscribe.com/mathscribe/jscurry-documentation.t...
- cturhan 11y agoThanks! I was looking For possible use cases 😊
- ternaryoperator 11y agoAdditional info on currying (and partial functions) in JS from Dr. Dobb's[1] [1] http://www.drdobbs.com/231001821 http://www.drdobbs.com/231001821
- cturhan 11y agoAlthough I like currying a method, I think it makes things difficult. Think about it, you joined an ongoing project and see curried functions and variables around. You have to check each function if it's curried from another to find original function (in this case `volume` function) and that might be exhausting. Without proper documentation for every curried function, it might be time consuming.
- edem 11y agoWhat does Project Decibel do?
- graememcc 11y agoFor those wanting to explore more, my library, funkierJS (http://www.graememcc.co.uk/funkierJS http://www.graememcc.co.uk/funkierJS, introductory blog post http://www.graememcc.co.uk/2015/06/07/onfunkier/ http://www.graememcc.co.uk/2015/06/07/onfunkier/) has built-in currying, and provides curried implementations of most ES5 built-in functions.
- taylodl 11y agoMemoization is a good use case for currying which I blogged about - https://taylodl.wordpress.com/2013/11/05/functional-javascript-currying/ https://taylodl.wordpress.com/2013/11/05/functional-javascri...
- exa 11y agoNice. I wrote an es6 version. https://gist.github.com/edge/af86d57fcadd21f9e587 https://gist.github.com/edge/af86d57fcadd21f9e587
- jdd 11y agoFor those interested lodash provides _.curry and _.curryRight methods with placeholder support as well. See https://lodash.com/docs#curry https://lodash.com/docs#curry.