6 ms·
Functional JavaScript, Part 4: Function Currying
- batmansbelt 12y agoSeems like a cool use of the technology.
- rpwverheij 12y agobeing a javascript/typescript developer with little knowledge about functional programming, this article renewed my interest in the matter. I would be interested to see some performance tests though, to get a feeling just how much 'a little performance cost' costs
- lrichardson 12y agoI was thinking about whipping together a perfJS for the curry function and putting it in the post but ended up not... I might make one and post it back here. --- UPDATE: I made this jsperf: http://jsperf.com/curry-function http://jsperf.com/curry-function There might be better things to test, but the results were actually unexpected. Currying "manually" with nested functions is actually slower than using my "curry" function. As I mentioned in my post, if you are touching the DOM at all, I really doubt you will see much of a performance hit from using this...
- rpwverheij 12y agointeresting! tnx for the extra effort to put that up.
- reconbot 12y agoUnless you're in tight loops you wont notice this perf hit. Disk, Network, and DOM manipulation will be the 99% time sinks.
- ceedan 12y agoThat doesn't justify degrading the rest of your code base.
- visarga 12y agoMe too. I thought long and hard about how I could use functional programming (aside from the usual callbacks) and came to the conclusion that functional programming is more composable - that's its strength - how you can combine stages in a pipeline. It's all centered around data flow instead of execution flow. One example would be jQuery or Underscore - how you can chain operations with the "." operator, or another common example is how we use the pipe " | " operator in the shell to compose processes. I use the latter quite a lot and am proficient at making pipelines. By contrast, procedural programming relies a lot on global variables and that makes a mess of reusing pieces of code from an app into another.
- chriswarbo 12y agoI wouldn't say that procedural code relies on global variables, but I would say that we find ourselves worrying about execution order a lot more when using procedures rather than pure functions. If we care about execution order, we tend to avoid higher-order patterns which may defer execution (eg. compose) or give a nondeterministic order (eg. map). If we avoid higher-order patterns, we end up hard-coding an awful lot of control-flow, which makes our code less re-usable. Simplistic attempts to avoid this, introduce a small amount of higher-order behaviour, like passing around 'callbacks', but this can be cumbersome and add a lot of boilerplate. Of course, the solution to avoiding this is to use even more powerful higher-order functions (like monadic bind) to handle the callbacks for us, but since we're trying to avoid as much higher-order behaviour as possible, this tends not to be done.
- chriswarbo 12y agoInteresting, especially aside about partial application ("holes") which I've not seen done in JS before. I've been using my own JS currying implementation in a few projects[1], which pretty much matches the implementation in the article: count the function's parameter number and build up argument lists in closures until we have enough to call the function, then pass them all in. However, when I was implementing the same thing in PHP[2] I discovered a nice alteration we can make: instead of passing all arguments to the curried function, we should only pass the minimum number. If we have any left over, we should pass them to the return value. This lets us chain even more things together[3]. For example: // "c" is our currying function var id = c(function(x) { return x; }); var triple = c(function(x, y, z) { return x + y + z; }); // These work with both currying functions id(triple)('Hell', 'o Wor', 'ld'); id(triple)('Hell')('o Wor', 'ld'); id(triple)('Hell', 'o Wor')('ld'); id(triple)('Hell')('o Wor')('ld'); // These only work with the altered version id(triple, 'Hell', 'o Wor', 'ld'); id(triple, 'Hell')('o Wor', 'ld'); id(triple, 'Hell', 'o Wor')('ld'); id(triple, 'Hell')('o Wor')('ld'); [1] http://chriswarbo.net/index.php?page=news&type=view&id=currying-in-javascript http://chriswarbo.net/index.php?page=news&type=view&id=curry... [2] http://chriswarbo.net/index.php?page=news&type=view&id=admin-s-blog%2Fpartial-application http://chriswarbo.net/index.php?page=news&type=view&id=admin... [3] http://chriswarbo.net/index.php?page=news&type=view&id=admin-s-blog%2Fbetter-currying-in http://chriswarbo.net/index.php?page=news&type=view&id=admin...
- lrichardson 12y agoThis is very interesting. I'm not so sure this wouldn't trip me up some times though. What if the first argument you are expecting, is supposed to be a function? I would think that would be a pretty common scenario... Unless I'm not quite getting what you mean? EDIT: I see that this would only happen if you passed in more arguments than expected, thus it wouldn't be a problem. Very cool! I might update mine to work this way.
- chriswarbo 12y agoIndeed, the behaviour should be identical when too few or exactly enough arguments are given. The way I see it, if we're not given enough arguments we introduce wrappers around our function to accept the rest. With this alteration, when we're given too many arguments we eliminate wrappers from around our result by passing them the rest. This hints that they might be 'dual' notions, in a Mathematical sense. Maybe I should dub it "cocurrying"? ;)
- ceedan 12y ago"In most web applications the "bottle-neck" is going to be DOM interaction. It is very unlikely that you will notice a performance hit at all." uhhh, 2 wrongs don't make it right? "It is very unlikely that you will notice a performance hit at all." Until you do.. -- I could never justify using this stuff at work. It's inefficient, unfamiliar to most JS developers and even if it did pass a peer review, nobody else would actually want to maintain it.
- chriswarbo 12y ago> I could never justify using this stuff at work. It's ... unfamiliar to most JS developers and even if it did pass a peer review, nobody else would actually want to maintain it. The same could be said for any API. Currying is actually very unobtrusive; the only time a curried function behaves differently to its uncurried counterpart is when it's given a 'wrong' number of arguments. The benefit is never having to perform manual eta-expansion (ie. any time we write a redundant wrapper like "function(x) { return bar(a, b, c, x); }"). As for efficiency, I default to having the machines doing things to save developer's time and only do things the other way around when it's untenable (or for fun).
- platz 12y agoSeems to me that using the functional style it's really important to document the types (done in the article with the //+ lines). Otherwise it will be hard to make the types line up. Also there are functions in javascript which do different things depending on what the type of the argument is (or do different things depending on how many arguments are passed in; those kinds of functions always have irked me), so it would be hard to document exactly what the "type" of those functions are.
- lrichardson 12y ago@platz, yes, this is correct. When currying and/or composing functions, it becomes more difficult to determine the input/output types of the function since the function signature won't be right there. I'm borrowing the `//+` syntax from Brian Lansdorf, since I saw him use it and quite like it. He mentioned he has "a lot to say on the topic" of commenting / type sigs, but I haven't heard him speak specifically on that. Also, for functions that do different things depending on how many arguments are passed in, you would never want to curry these. The curry function relies on the function passed in expecting a specific number of arguments.
- platz 12y agoPart of me thinks that if you're going to pursue this style to it's extreme, you might as well just be better off using something like purescript http://purescript.readthedocs.org/en/latest/intro.html#hello-purescript http://purescript.readthedocs.org/en/latest/intro.html#hello...
- lrichardson 12y agoI haven't had a chance to try purescript yet, but I have looked at it. Really though, I love JavaScript, and don't have much desire to use another language that that transpiles into it...
- platz 12y agoFair enough, there's always headaches with transpilers; I for one have been suspicious of all the coffeescript+derivatives.
- ripter 12y agoAwesome job! I was trying to understand curry by learning Haskell but you summed it up perfectly. What is the difference between curry and partial? Is a partial just a curry that hasn't filled in all the arguments yet?
- chriswarbo 12y agoI assume you mean "partial application"? "Currying" turns a multi-argument function into a chain of one-at-a-time wrappers. If we curry a 5-argument function "foo(a, b, c, d, e)", we get a nesting of 5 functions, each accepting one argument "foo(a)(b)(c)(d)(e)". The implementation in the article, as well as my own, go slightly further and also allow curried functions to accept multiple arguments at once, eg. "foo(a, b, c, d, e)"; this is just for convenience though, a "real" curried function must accept arguments one at a time. Note that in Haskell syntax, there is no difference between the two: "foo a b c d e". Alternatively, "partial application" takes some argument values now, returning a function which accepts the rest later. If we partially-apply a 5-argument function with, say, its first 2 arguments, we get back a 3-argument function, eg. "papply(foo, a, b)(c, d, e)". Note that we must use partial application explicitly whenever we want to supply a few more arguments; curried functions keep track of this themselves. As an aside, you mention Haskell. Note that Haskell functions are already curried; in Haskell, "curry" and "uncurry" convert between functions which accept separate arguments and functions which accept one big tuple of arguments.
- chc 12y agoAs it exists in Haskell, yes, that's what partial application is — the result of applying a curried function to less than the full number of arguments. Some other languages have a more general form of partial application where you can partially apply arguments in non-sequential order, which isn't related to currying and instead just means creating a wrapper function that calls the original function with certain arguments filled in.
- taylodl 12y agoAnother motivation for currying in JS is to memoize the curried function which I describe in my post http://taylodl.wordpress.com/2013/11/05/functional-javascript-currying/ http://taylodl.wordpress.com/2013/11/05/functional-javascrip... I never provided a currying function, thinking there were so many implementations easily available - but I've never seen one as good as what's presented here!