7 ms·
How currying leads to elegant, readable code
- phillc73 12y agoI favour a nice hot Rogan Josh. Helps me write excellent code the next morning. Not so the half a dozen pints I had along with it.
- pettazz 12y agoCurrying is the process of turning a function that expects multiple parameters into one that, when supplied fewer parameters, returns a new function that awaits the remaining ones. I think this, along with the whole article, is the best explanation of currying I've seen on the whole damn internet.
- cowbell 12y agoIt's also wrong :) http://www.uncarved.com/blog/not_currying.mrk http://www.uncarved.com/blog/not_currying.mrk Which the author freely admits: (Some will insist that what we're doing is more properly called "partial application", and that "currying" should be reserved for the cases where the resulting functions take one parameter, each resolving to a separate new function until all the required parameters have been supplied. They can please feel free to keep on insisting.)
- CrossEye 12y agoYes, freely admitted. (Author here.) I feel that although the distinction can be very important, it's much less so for Javascript. And the implementation in Ramda can be used that way any time you choose: var total = reduce(add)(0); // or var sum = reduce(add)(0)(numbers) works just as well in Ramda as var total = reduce(add, 0); // or var sum = reduce(add, 0, numbers) So, as I said, feel free to keep on insisting. :-)
- cowbell 12y agohttp://vimeo.com/96639840 http://vimeo.com/96639840 Start at 26mins. You don't have flip. You lose control over partial application. It isn't curry. You're right. It is an important distinction :)
- comex 12y agoThis implementation of multi-argument partial application does not in any say prevent implementation of flip. It's just syntax sugar.
- CrossEye 12y agoI finally got a chance to watch that video, and it looks as though what you're worried about is `flip`. Ramda for one already includes a version that works much like Haskell's. I don't know if this satisfies your objection, though.
- tel 12y agoAs I stated above... flip is well defined so long as your arguments are positional.
- tel 12y agoAs long as you have ordered parameters, I'm not sure this is such a big issue. First, tuples are (morally, i.e. up to non-termination) associative in most reasonable languages, so the product/exponential adjunction can be shifted around however you like -- all morally identical a -> b -> c -> d a -> (b -> c -> d) a -> (b -> (c -> d)) (a, b) -> c -> d (a, b, c) -> d ((a, b), c) -> d (a, (b, c)) -> d However, so long as you have parameter order (we're not allowed to commute our tuples, just reassociate nested tuples like ((a, b), c) <-> (a, (b, c)) and so on) then flip still morally works flip (someFn :: (a, b, c) -> d) :: (b, a, c) -> d :: (b, a) -> c -> d :: b -> a -> (c -> d) The same story holds for partial evaluation. Of course, there have to be far more edge cases to handle all this tuple twiddling. --- Really, I think it's pretty meaningful to blur the distinction between "curry (once)" and "curry repeatedly along with tuple reassociation". The later forms an equivalence class of function types which all have the same application behavior.
- tel 12y agoAlso to explain that strange "adjunction" comment, currying results from the notion that the things of type (a, b) -> c are exactly equivalent to the things of type a -> (b -> c) An adjunction (F, G) occurs when the things of type (F a -> c) are equivalent to things of type (a -> G c), so if we pick F x = (x, b) and G x = (b -> x) then we see that currying is an adjunction between (,b) and (b ->).
- evincarofautumn 12y agoOne intuition for this is that (a, b) is the product type (a × b) and the function arrow (a → b) corresponds to exponentiation (b^a), so you get the isomorphism for free from the identity c^(a × b) = (c^b)^a.
- orkoden 12y agoCurrying can be hard to spot if you're not used to it. Especially in a language with inferred or dynamic type, it's not obvious that the var contains a function, not a result object. var result = somefunc(a,b) // object var otherResult = somefunc(a) // function It gets even more confusing if overloading of function names for different parameters or different number of parameters is allowed. So elegant: maybe. Readable: no. Depends on the programming language.
- VogonWorkEthic 12y agoI second that. Just adding unnecessary complexity to save on a few lines of code that are so common most people don't even spend mental effort processing them. Basically, it seems to me like trying to fix something that isn't broken.
- the_af 12y agoDon't know about how it's used for dynamic languages (it might be that it makes them harder to understand), but in languages with static typing, the purpose is not to save a few lines of code. The purpose of currying is to improve reuse and chaining of functions, resulting in easier to read and more elegant code.
- alttab 12y agoAnd this works as long as everyone is fully committed to functional programming and you have little to no turn over or a small dedicated team. The minute you step into the standard industry things change a little bit. I once maintained an amazing piece of javascript that did dynamic server requests and rendered graph data, processed user input, changed views, and make lasting configuration changes to the database. The first 30 or so lines of code was simply setting up the functional haskellish environment for javascript, and the last 40 lines + 40 lines of HTML put everything else together. It was terse, compact, elegant, and impossible to maintain or make wider changes after the original author had moved on. It was tightly contained into one piece of the code, so not something that will be seen on the mainline easily. Going in and fixing any tiny little bug forced any unfortunate developer to immediately learn Chinese. Someone was tasked to re-write it so the rest of the team could maintain it effectively. I like ruby/python a lot because it meets in the middle fairly easily, and doesn't require contortions to the natural intent of the language to get rid of cruft.
- KMag 12y agoIf I'm writing a function where there isn't an obvious order for the arguments, I chose the order such that Currying is most useful, even if the language I'm writing in doesn't support Curried functions/partial function application. You never know when someone will port your code or create cross-language bindings. For instance, my company writes a lot of code in a proprietary language and the other day I was writing a function to generate random strings, given a length and an "alphabet" string from which to choose characters. I chose the ordering: randomString alphabet length = ... so that if partial function application were added to the language, one could simply write the functions randomHex and randomBase32 as: randomHex = randomString "0123456789ABCDEF" randomBase32 = randomString "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" The alternative ordering would allow me to easily write functions that generate fixed length strings and take their alphabets as inputs: random10Char = randomString 10 random4Char = randomString 4 This ordering is much less useful in general, so I chose to make the alphabet the first parameter. On a side note, our language also has some time series extensions for dataflow programming. There's an API for efficiently adding new data flows based on something similar to template specialization, but the API is unwieldy and the source of much confusion. So, I wrote a wrapper API that uses reflection to expose the same functionality as partial function application / Curried functions over time series.
- CrossEye 12y agoI've done that too, then had to wave my hands about why I choose that parameter order, when I know damn well the languages isn't going to change... "But just in case...." :-)
- joeyh 12y agoIt's an interesting design question to pick orderings that work best with currying. Oddly though, when writing haskell code, I don't worry about it much. Once I find myself needing to `flip` a function before using it, I have determined experimentally that the original parameter order I picked was not the best one, and I then go change the function, and let the type checker walk me through fixing any necessary call sites (which also verifies if the new parameter order is better or not).
- donpdonp 12y agoUpvoted just for the quality of the pun.
- CrossEye 12y agoHave to thank/blame @buzzdecafe for that!
- twic 12y agoIs the final code equivalent to: var getIncompleteTaskSummaries = function(membername) { return fetchData().then(function(data) { var incompleteTasks = []; for (i = 0; i < data.tasks.length; ++i) { var task = data.tasks[i]; if (task.username == membername && !task.complete) { incompleteTasks.push({id: task.id, dueDate: task.dueDate, title: task.title, priority: task.priority}); } } incompleteTasks.sort(function(a, b) { return a.getTime() - b.getTime(); }); return incompleteTasks; }); } ? Apologies if that's wrong, i barely know JavaScript. That code is not so great, and a bit '90s, but it's not appalling. The horrifically verbose non-currying code in the article isn't something i see any need to write. It looks like an outrageous strawman to me. My code here could definitely improved with some functional idioms for the looping, picking, and sorting, but i wouldn't reach for currying here.
- CrossEye 12y agoYes, at first glance, that's pretty close. (You'd have to do something about `.getTime()`, as the original just did lexicographic sorting on date strings.) The article really didn't get into the fact that these blocks of code that were listed inline would ideally represent reusable functions. That these functions would best be defined separately and only included by reference would have cluttered the article, but leaving them out does make it feel a bit like a strawman, I agree. The breakdown of a task into these smaller functions is something I discussed more fully in the previous article: http://fr.umio.us/why-ramda/ http://fr.umio.us/why-ramda/
- skybrian 12y agoThis is perfectly good style, and better than the original article. Don't listen to the functional language fans. Replacing the for loop with its functional equivalents would make it less readable.
- jamii 12y agoCurrying with map/reduce/filter can be hard to read, but list comprehensions are pretty nice: (defn get-imcomplete-task-summaries [membername] (.then (fetch-data) (fn [data] (sort-by :time (for [task (:tasks data) :when (= membername (:username task)) :when (not (:complete task))] task ;; immutable, no need to copy ))))) It makes me a little sad that functional programming has come to mean map/reduce/filter for lots of people, rather than avoiding mutation which is far more interesting.
- faldore 12y ago"Ok, here we go. Does code like this look at all familiar?" No, because I use Array.prototype.filter(), Array.prototype.map(), and Array.prototype.reduce() to accomplish these things...
- CrossEye 12y agoYeah, I probably shouldn't have cribbed the example from my old "Intro to Functional Programming" talk...
- dustingetz 12y agoBasic point free style as described in the article is alright in javascript. Extreme point free style does not work in javascript, AT ALL!!!! Two reasons: 1) Find the bug: the table sort is glitchy. Is the bug in this suspicious line, or somewhere else? (Real line of code in a codebase I inherited, and yes there is a bug in it) var sorted = rows.sort(_.unsplat(_.pipeline( _.partial(_.map, _, accessor), _.splat(comparator)))); 2) I use React, so I can get away with a lot of really functional stuff since React is well suited for functional javascript. React is a great functional abstraction, but under the abstraction is imperative code. Imperative code means you need a debugger. When you write in a point free style, there's nowhere to place a breakpoint!
- CrossEye 12y agoIt is harder to debug points-free code. I find myself doing more debugging with `log` statements in a long pipeline than I do with other styles of code. On the other side, I tend to work with a few well-tested functions over and over and end up with fewer problems to debug when I work in a functional paradigm, points-free or not. On rare occasions, when I need to debug something that is difficult because it's points-free I simply add some explicit parameters, either temporarily to enable debugging or permanently. Points-free is nice, but should never be an overriding goal.
- anon4 12y agoI'm generally a fan of currying, but specifically when used with javascript, I think it can be problematic. The default behaviour of any function when called with fewer arguments than specified is to set named arguments to undefined. Meaning, functions in javascript always take any number of arguments and changing that default behaviour is confusing. There is also the matter of the "this" parameter. I think that using the .bind() method on functions is the right approach. It will lead to less surprising behaviour in the context of javascript.
- aaronblohowiak 12y agoCurrying is the factory factory pattern for λ people.
- Demiurge 12y agoI'm probably missing something, but why not just do...? var jp = function(last){ return formatNames2('John', 'Paul', last); }
- CrossEye 12y agoYou can if you want: var jp = function(last) { return formatNames2('John', 'Paul', last); }; var je = function(last) { return formatNames2('James', 'Earl', last); }; var lh = function(last) { return formatNames2('Lee', 'Harvey', last); }; var c = function(middle, last) { return formatNames('Clarabelle', middle, last); }; But I find this cleaner: var jp = formatNames2('John', 'Paul'}; var je = formatNames2('James', 'Earl'}; var lh = formatNames2('Lee', 'Harvey'}; var c = formatNames('Clarabelle'};
- Demiurge 12y agoShould you really have jp je lh and c at all than? Having so many different functions seems like a problem on its own, and maybe there should just be an array of names and a function iterating over them...
- CatMtKing 12y agoIt's a toy example; he's just trying to show how currying works, rather than why you should use it. If anything, it does show that one useful thing about partial application is that you can write a generic function (formatNames) and then make it more specific (formatNames("John")) without repeating yourself.
- detrino 12y agoNote that currying is not necessary for partial application, here is partial application implemented in C++14: http://coliru.stacked-crooked.com/a/bed4a4fdd6e856b0 http://coliru.stacked-crooked.com/a/bed4a4fdd6e856b0 However, I find lambdas with the extra parameters repeated more readable than partial application.
- liricooli 12y agoI have no experience neither with JS nor with functional programming, but know Lambda calculus and currying from Discrete math courses. As Currying and functional programming a popular thing now, I was thinking what my current codebase would look like in C# if I were writing code in a more functional way. So, obviously C# isn't a functional languageat all but using Func<T>, and maybe some delegates would enable this easily. I'm not sure this would turn my code to more elegant or readable. What would I benefit from ?
- CrossEye 12y agoI'm not really a C# person, but from what I know of the language, although it has some lambdas and some other functional features especially around LINQ, functional programming really is against the grain of the language, and would not be easy to integrate into the same code. The .NET platform of course has F#, which is a dedicated functional language. Some day I'll spend some time learning this one... perhaps.
- Finster 12y agoThe "curried" code seems less readable and maintainable than the original code. Is this like dependency injection where I have to make my code harder to read and maintain to satisfy some technical definition of "loosely" couple?
- aResponder23 12y agoIt'll seem less readable at first, yes. I've seen a codebase transformed by currying. We were able to remove six functions from one webservice interaction service, and handfuls throughout other parts of the code, and have made debugging and maintenance easier. The code is a little more confusing at first glance. The developers who replaced the code with curried functions didn't inform the whole team what they were doing. There were a lot of wtfs until they clarified. Now we can read it, and I know I'll be looking for places for partial applications.