10 ms·
I've actually grown to dislike currying, it seems like a neat idea when you start using it, but whenever I come across it in real life code it just results in l
by tomxor 5y ago
I've actually grown to dislike currying, it seems like a neat idea when you start using it, but whenever I come across it in real life code it just results in layers and layers of yuk - making me want to scream "show me the fucking code", you can go through 100s of lines before seeing a single operator...
I'm not a big fan of OOP either, the older I get the more I end up writing a cross between procedural and functional code with the least possible abstractions in the way - while also trying to find the smallest natural coupling between blocks of code.
Patterns are very subjective, and they usually have a place where it's objectively good to use them - but I'm not sure about currying, it feels more like a hack for when you can't do what should be done to the underlying code.
- qsort 5y ago> I've actually grown to dislike currying I believe this is a notational issue more than anything. Fixpoint style in languages like Haskell is very natural and intuitive, if you have to write a `curry` function like the article shows, you have already lost. It's much more convenient to `ad-hoc curry` with things like: const f = x => myFunc(someArg, x, someOtherArg); return myArr.map(f).whateverElse();
- masklinn 5y agoCurrying makes sense if haskell because it’s a curried langage, so you may need the odd uncurry, then curry it back to normal. In uncurried langages, generic curring has few use cases. Partial application does, and some libraries may want to provide both curried and uncurried versions of some things, but generally “curry” itself is unhelpful.
- kortex 5y agoWhat's the difference between currying and partial application/closures?
- masklinn 5y agoCurrying is the conversion of a n-ary function to a chain of n 1-ary functions. So if you have a function `(a, b, c) => d` (a 3-function which returns a result) and you curry it it becomes a => b => c => d (a 1-fn which returns a 1-fn which return a 1-fn which returns a result). Partial application is the application of an n-ary function to k arguments (k <= n, often but not necessarily l < n) without actually evaluating (calling) the function, so given the same `f = (a, b, c) => d`, `partial(f, 1, 2)` returns a function `c => d`. A closure is a function which "closes over" its lexical initialisation context and carries it along. A curried function is trivially partially applicable, just call it with less than `n` parameters and it'll yield a (also curried) function taking `n - k` parameters. It's useful to build a language on that principle, it's not really useful to provide that as a generic utility, because in most scenarios you'd rather just partially apply a function. A closure is a completely freeform tool so it can do more or less anything you want.
- brundolf 5y agoThe distinction sounds almost completely syntactic. I.e. foo(a, b) vs foo(a)(b). Is the former not just sugar for the latter?
- masklinn 5y ago> Is the former not just sugar for the latter? Theoretically they're equivalent, but practically not really especially when you introduce more complicated "imperative" features e.g. default parameters or overloading. They also have API impacts e.g. because Haskell is curried, its HoFs are generally of the form `fn param`, that way you can trivially partially apply the callback and use the same partially applied function to different parameters. In method-based languages, there is no way to partially apply the callback, and in uncurried languages in general since partial application is a secondary concern you'll often see the callback last regardless so the non-callback parameters don't "get lost" at the tail end of a long callback.
- brundolf 5y ago
- neurotrace 5y agoI've felt this same sort of pain but I don't blame currying. It's when currying and partial application is used for deep dependency injection with no easy route to finding the original function. I dabbled in writing an extension for F# that would trace back function calls to give you all of the possible original sources for a value. I think that would solve that particular problem. Currying and partial application are really nice when you have a pipeline operator, which is why the partial application and pipeline operator proposals are so intertwined. users |> List.map (fn user -> user.Email) |> sendEmail |> Result.mapError (fn _ -> "Error sending emails")
- okasaki 5y agoI don't see why that's better than the good old for loop. errors = list() for user in users: if not sendEmail(user.email) errors.append("Error sending emails")
- neurotrace 5y agoThe example I gave was contrived. I frequently find myself modeling much of my logic as a series of data transformations. The more I can model logic like this, the easier it is to test. For what it's worth, your example doesn't map 1-to-1 to the intention of what I wrote. Doing it in a more imperative style, I'd have to write emails = list() for user in users: emails.push(user.email) res = sendEmail emails if res.IsError: Error("Error sending emails") else: res (excuse my pseudo Python)
- BiteCode_dev 5y agoExcuses accepted. Imperative Python would really look like: try: return sendEmail(user.email for user in users) except IsError: Error("Error sending emails") # if it does something I agree FP pipelines are nice, but not everything needs it.
- HugoDaniel 5y agonice one, however shouldn't good old for loops be done in ALGOL instead?
- somehnacct3757 5y agoI mostly agree but every tool has its purpose. Currying is indispensable in some composability workflows. Consider advanced component creation in virtual dom frameworks. Components may want to yield some render responsibilities to the end-user while not exposing their inner api. They can use currying here to hide details from the end-user while providing their own public api. I've also seen it be useful when you have sub components editing different parts of a model. You write one generic prop-assigner setModelProp(prop, val) alongside the model in the scope that owns the model. Then you give each subcomponent a curried setter with the prop locked in. Those sub-components don't have the ability to edit the model any other way now, and you haven't written a ton of boilerplate setters.
- masklinn 5y ago> every tool has its purpose. That's really not true. The Y combinator is not generally useful for instance, because every modern language supports recursion. > Currying is indispensable in some composability workflows. It's not. > Then you give each subcomponent a curried setter with the prop locked in. That's partial application, not currying. There are convenience use cases for libraries providing curried versions of some functions e.g. you might want to provide both `setModelProp(prop, val)` and `setModelProp(prop)(val)`. But that's because you expect significant use cases fo partially applied versions of the utility functions. Currying is not necessary for this, it just makes things more convenient for users.
- somehnacct3757 5y agoI see, I had these two concepts conflated. I guess the difference is currying returns a function and partial application invokes the function. In that case I don't know any indispensible situations for currying.
- DemocracyFTW 5y ago> The Y combinator is not generally useful for instance I wish I had been told that before trying to understand and actually use it. I once wrote a JS benchmark based on Rosetta code to compute Fibonacci numbers. Not only is the Y combinator code convoluted and quite hard to understand, it's also between 1e5...2e5 times slower than a plain iterative implementation. Somehow I find it ironic to post such a result here.
- ItsMonkk 5y agoYep. I'm reminded of the advice "Favor Composition over Inheritence", which begs the question "When can we not use Composition and need to fall back down to Inheritence?" and it turns out the answer is that you can always use composition with the exception if you are relying on someone else's library that does it wrong and you can't change it. Dependency injection and currying both do the same thing, and they are both useless most of the time just like Inheritence. You want to avoid nesting as much as possible. We should be rejecting dependencies. We should be returning values and passing them through functional pipelines.
- monkeyfacebag 5y agoI'm not following. How is dependency injection like inheritance and also useless?
- sfblah 5y agoI think GP is saying that dependency injection is like currying, not that it's like inheritance.
- ItsMonkk 5y ago"Favor composition over inheritence" is like "Favor pipelining over currying".
- monkeyfacebag 5y agoI'm still not getting the relationship to dependency injection. What is pipelining?
- ItsMonkk 5y agoDependency Injection is just another way to curry. In both you are passing an argument. SPBS's post[0] is very similar. All together, "Favor composition over inheritence" is like "Favor pipelining over currying/dependency injection". There's an example of a pipeline down the thread in p2t2p's comment[1]. [0]: https://news.ycombinator.com/item?id=28583343 https://news.ycombinator.com/item?id=28583343 [1]: https://news.ycombinator.com/item?id=28584270 https://news.ycombinator.com/item?id=28584270
- layer8 5y agoOne aspect I don’t like about currying (at least with the usual syntax) is that it privileges the first parameter of each function. As soon as you want to curry on the second parameter, you lose the "natural" currying syntax and have to fall back to e.g. `half x = \x -> div x 2` or something like `half = flip div 2`. That aspect, to me, makes currying nonuniform and awkward, as in many cases the first parameter doesn’t have a particular conceptual distinction to single it out for currying. It’s a bit similar to how I don’t like Smalltalk’s asymmetric syntax for commutative operations (e.g. `x add: y`).
- josephcsible 5y agoYou can curry the second parameter without it being too ugly like this: half = (`div` 2) Also, I think the idea with curried languages is that you're supposed to write your functions with the parameter that people are most likely to want to curry first.
- layer8 5y agoOf course that’s the mitigation strategy, but my point is that the usual currying syntax inherently special-cases the first parameter, and in x% of the situations that’s not what you want. From a language-design point of view it just feels awkward.
- josephcsible 5y agoI guess that's fair. What about Scala's _ syntax, though? Does it solve the problem?
- mLuby 5y agoAgreed. I've come to view partial application as the original coder saying "I guarantee you'll never need to inspect or modify this parameter from now on." Obviously, that's rarely true in practice. What about partial application of named parameters?
- 5y ago
- danlugo92 5y ago> I get the more I end up writing a cross between procedural and functional code with the least possible abstractions in the way - while also trying to find the smallest natural coupling between blocks of code. Same here. I think this is a correct approach, sprinkled with some reactive code here and there (prefer reactive over other abstractions such as 2-way binding et al).
- swman 5y agoUgh my team at work is obsessed with DDD for a service with two endpoints. Absolutely overkill and the levels of OO abstraction are terrible. If this were more complicated I’d understand, maybe I’m wrong in general. But writing a small Nodejs service in such a way is terrible IMO.