Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



I'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")


> Currying and partial application are really nice when you have a pipeline operator

Currying != partial application.

And while currying is useful in curried languages (to convert back from uncurried to curried), and partial application is useful period, the question is whether currying is useful, in general, in an uncurried language.

I don't think it is:

* You usually want to perform partial application, currying is an unnecessary intermediate step.

* Currying conflicts with rich "imperative" parameters e.g. overloading, default parameters, keyword parameters.

* Uncurried languages are usually imperative and effectful, that you have filled all the parameter spots does not mean you want to invoke the function.

Having a curried language is neat, and in curried languages converting back from uncurried to curried functions is tremendously useful. But it's not so for uncurried languages (which is most of them).

> which is why the partial application and pipeline operator proposals are so intertwined.

Again, partial application != currying. And pipeline operators can perform partial application (implicitly or explicitely) on their own. For a flagrant example, just see Clojure:

    (->> users
     (map :email)
     (sendEmail)
     (mapError (constantly "Error sending emails"))


I'm not sure I fully appreciate the distinction you're trying to make here: "currying != partial application". Can you clarify?

In particular I don't know what's meant by "curried" and "uncurried" languages


In Standard ML and Haskell, all functions are curried by default -- they take a single argument, and the syntax for defining multiple-argument functions is just sugar for defining single-argument functions that return single-argument functions. That's reflected in this Haskell type signature for a function adding two integers:

   add :: Int -> Int -> Int
This is a function that takes one integer and returns a function which itself takes another integer and then finally returns an integer. Syntax sugar allows you to define it and call it like a single function.

An uncurried function in these languages is a function that takes multiple arguments as a tuple.

   add :: (Int, Int) -> Int
You don't see a lot of functions defined this way in Haskell because there isn't really any advantage to doing so in a lazy, pure language. It's more common in Standard ML from what I've seen, because SML is strict and impure.


> I'm not sure I fully appreciate the distinction you're trying to make here: "currying != partial application". Can you clarify?

Currying is the conversion of an n-ary function into a chain of 1-ary function e.g. `fn(a, b, c) -> d` becomes `fn(a) -> fn(b) -> fn(c) -> d`.

Partial application is what it says on the tin, it only applies k parameters to the function returning an (n-k) parameters function, without actually running the function.

Currying is one method allowing partial application, but not necessarily the only one, or usually the best fit as it tends to not interact well with features like overloading, default parameters, keyword parameters, …

> In particular I don't know what's meant by "curried" and "uncurried" languages

Languages like Haskell, OCaml, or Elm are "curried" by default: when you define an n-ary function it's really just sugar for a chain of 1-ary functions; and the application of multiple arguments is similarly the implicit application of single arguments multiple times e.g.

    map : (a -> b) -> List a -> List b
    map f l =
      case l of
        [] -> []
        (h::t) -> (f h) :: (map f t) 
can be called as

    map fn [1, 2, 3]
or

    (map fn) [1, 2, 3]
the semantics are identical, which is not the case in an uncurried language like Javascript or Python:

    map(fn, [1, 2, 3])
and

    map(fn)([1, 2, 3])
will behave very differently unless the implementer has taken special steps to handle this case e.g. dynamic behaviour based on an optional second parameter, overloading, etc…


> applies k parameters to the function returning an (n-k) parameters function, without actually running the function

What would be the use of this? Just delaying the execution of the function's side-effects?

I'll tip my hand: I'm working on a JS-like (and JS-targeted) language that I want to support partial-application/currying by default. Under the hood everything is represented in a curried way, but syntactically you can both define and invoke/partially-invoke functions with the more familiar syntax (commas). But this is fully superficial right now; it all happens at the parser level. Is there a downside to doing things this way?


The downside is that curried functions make default, optional, and keyword parameters difficult to implement.

For defaults/optionals, given "fn f(x, y, z=10)", if "f(1, 2)" evaluates to "f(1)(2)", does that return a function or the result of "f(1)(2)(10)"? What if you have "fn f(x, y = 10, z)"? All of this is a lot easier if you can evaluate the full argument list at the time of function invocation.

As for keyword arguments, they just don't play well with any kind of enforced ordering.


Got it.

I wonder if that ambiguity could be resolved using contextual type info? I.e. if one of the two interpretations fits the expected type, use that. Otherwise, type-error.

Maybe that would be confusing, not sure.


If you come from typical oop background like java. Imagine there is a function has 5 parameters, and you need to to provide the first 3 first. Then later on you are using this partially applied function somewhere else. You can even reuse it later with different 2 params. Compare to class, it's just like how you initialize an object with certain values, then do something else with this object.


Bagel?

I read your post yesterday. Good luck with your language!


Yes, thanks! :)

To be honest this comments section has me seriously reconsidering the "everything is partially-applicable by default" feature, haha.


At a deeper level currying is just a specific application of the Hom-tensor adjunction to the category of Sets. In particular the curried function is equivalent to the original. Partial function mappings is a restriction, and inherently not a injection. That is, generally the partial function is not in an equivalent Hom space to the original function.


そう言う事ですね〜ッ。は〜い


> Currying conflicts with rich "imperative" parameters e.g. overloading, default parameters, keyword parameters.

This doesn't contradict anything you said, but I think Idris is a language that curries everything and has default parameters.

https://docs.idris-lang.org/en/latest/tutorial/miscellany.ht...


Agreed, and I really with partial() was a builtin in Python, instead of having to fetch it from functools. partial() is useful regularly, and much more explicit/flexible than a lambda for this specific use case.


I 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")


For me, the functional/pipeline/call chain style changes perception from "how" to "what".

Because I say "what" do to do without going into details of "how" I feel like I have spent less mental capacity reading this portion of code.

However, it does require your transformations to be simple and composable, shove a giant multi-line lambda in there it yes, the loop will be better.


The 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)


Excuses 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.


nice one, however shouldn't good old for loops be done in ALGOL instead?


Yes! Like having both pattern matching and tagged union type in a language, it is the synergy of the features that makes them significantly powerful.


Oh, the lengths people go to avoid OOP. Java:

    users.stream()
        .map(User::getEmail)
        .map(Email::send)
        .map(res -> "Error sending email: " + res)
        .collect(toList())


What's "object-oriented" about that code? I'm suspicious of vague labels like "OOP" and "FP", but even some of the Java docs call Java's streams "functional-style".

https://docs.oracle.com/javase/8/docs/api/java/util/stream/p...


the only issue with java in this context is that you'll need to create a new class/transfer object between each function call that does things.

with elixir (the previous example) you'd likely use simple hashmaps or lists like {email: "mail"} or [:error, "reason for failure"].

That doesn't sound like its amazing, but actually is in practice, because the whole language is written with that in mind (pattern matching to effectively do function/method overloading depending on the value of each argument for example)

its very concise while still being very explicit. The same would be possible with java, but you'll need to create a lot of classes/interfaces/enums/boilerplate to facilitate it.

i'd still prefer java any time at a dayjob though, because boring and dumb is generally better if you need to write code that's gonna be in use for decades... and likely going to be changed by a lot of people with varying levels of experience.


Side note to say that the previous example is actually F#, a static typed language contrary to Elixir (to be fair Elixir was heavily inspired by F# and they look similar when only looking at snippets).


> the only issue with java in this context is that you'll need to create a new class/transfer object between each function call that does things

No, in most cases you don't. The stream interface is mostly designed around simple Lists, Sets and Maps. It also interacts very well with the Collection framework (e.g. myHashmap.entrySet() yielding a set, etc.) which is part of the standard library.

You can extend streams with custom collectors, but rarely if ever you need to define intermediate data structures. You do need to define initial and terminal structures, but I'd argue that's good practice regardless.


> No, in most cases you don't

that lets you pass the result from each function to the next without explicitly stating what form they have, yes.

You'll still be missing the conciseness/explicitness because the language isn't meant to be used like that and is missing necessary features in order to facilitate it such as pattern matching by the passed in values into function.

to make a simple example, you could theoretically write the following pseudo-code

  def sendEmail({email}) do
    // send email
  end
  def sendEmail({id}) do
    // get email address
    sendEmail({email})
  end
  sendEmail({id: 244})
or to make sure your code stops executing if an error occurs (positional return values this time)

    [:ok, msg] = sendEmail(lkajsdf)


    .collect(Collectors.groupingBy(...))


that does something entirely different.

maybe actually learn to program sometimes, then you will figure out what it does.


We were hoping for similar operators in JavaScript (pipeline `|>` and partial application `?`). However TC39 just decided earlier this month that we users of the language are not worthy of such powers. This was off course meet with a lot of resentment from many JavaScript developers, including my self.

https://github.com/tc39/proposal-pipeline-operator


Considering what happened with the monadic promises, that was to be expected. I hope we'll at least have the immutable records and tuples so that the JS engine can implement them efficiently. This way JS will become an even better compilation target.


You usually want to send the e-mail in order, with a sleep in between to not overload the e-mail server, and stop at any error, so that you can fix the error and then continue from where the error was.

An error could be a malformed e-mail address, or a timeout from the e-mail server.

What you do not want is to send 10000 e-mail, then have an error like "Error sending emails", then re-run a few time, only to have some people receive 5 e-mail, and some people receive 0 e-mail.


I didn't think I had to make this clear but a lot of people seem to be getting stuck on the specifics of my example code. That example is doing nothing but showing a 5,000 foot view of what pipelines can do. Please don't take my dumb example that was written early in the morning as The One True Way of processing data, sending emails, or handling errors. It's a horrible example of that


Simplified examples always look neat. Any code/syntax will look simple if it doesn't have async, state and error handling.


I can't be bothered to type all of it out on my phone but even adding in those pieces, it doesn't change it much. This is F# so you can easily manage the async via an async computation expression, the retry logic can be encapsulated in the `sendEmails` function, and the error handling is reduced to a `Result` which is very easy to work with in a pipeline. Not everything should be done in a pipeline but pipelines make a lot of things a lot nicer


Yep. 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.


lmao, is this satire? We should be using whatever abstraction is easiest to reason about. Sometimes that's apeasing the primate brain with objects and things, sometimes it's shoving those things through a pipeline.

Free yourself from the dogma, just do what makes sense.


> We should be using whatever abstraction is easiest to reason about

I dare mention Rich Hickey and his talk “Simple Made Easy”, and postulate that we should be using whatever abstraction is _the simplest_ (not the easiest one) to reason about, to which the grand parent rightfully refers, as far as I understand.

Objects are easy to many, but far from being objectively simple.


I would have linked the Principle of Least Power[0], but same idea. Rich certainly fleshes the idea out with more examples and I deeply respect his opinions.

I can't find it but there once was a blog that expressed this as: Reddit is a site where you can talk about anything with the exception of a few banned topics. Voat(might have been another reddit clone?) is a much less popular site where you can talk about anything. What do they talk about on Voat? Only the few topics banned on reddit.

While is strictly more powerful than for, for is strictly more powerful than foreach, foreach is strictly more powerful than map. And yet 95% of the time, the power in map is sufficient. Therefore 95% of the time you should use map. When you encounter a foreach, you should be expecting non-purity. When you encounter a while, you know that it's doing some recursive operation that requires that power.

This let's you reason about it. This allows you to compose these less powerful things.

[0]: https://blog.codinghorror.com/the-principle-of-least-power/


Rather than that I should've written, "we should use whatever abstraction makes the problem easiest to reason about".

I have no preference for either fyi.


I'm not following. How is dependency injection like inheritance and also useless?


I think GP is saying that dependency injection is like currying, not that it's like inheritance.


"Favor composition over inheritence" is like "Favor pipelining over currying".


I'm still not getting the relationship to dependency injection. What is pipelining?


Dependency 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

[1]: https://news.ycombinator.com/item?id=28584270


But why do you have to curry to inject your dependencies? Dependency injection is satisfied by moving internal constructs in your function or method into parameters, no?


That's the style that functional programmers prefer. It makes it slightly easier to work with as you can now pass them together as one. Most OOP dependency injection is done through interfaces, but with the functional approach where you have functions as first class citizens you can just pass functions around. And as the best interface is a one function interface, that's the ideal way to do it.

The point being, essentially they are the same thing, they hold the same power, and it's just a stylistic difference.


I’m still not seeing it. As a Java dev by day, I see DI mostly through the lenses of Hollywood principle and IOC containers, neither of which bear much resemblance to currying (as I see it). When it comes to something like DI frameworks, currying is certainly not as powerful, but I don’t think that’s what you mean. Currying seems much more related to something like the builder pattern. Maybe there’s some article you know of that I could read? Googled around but didn’t find anything.


So there is two types of DI, one way automatically wired through an IOC container, and the other manually wired through constructors. They both are the same thing, but one uses some configuration and magic to make it easier to pass around these interfaces.

Mark Seeman's blog[0][1][2](many more related articles, these are particularly related) helped me grasp some of this stuff.

[0]: https://blog.ploeh.dk/2014/06/10/pure-di/

[1]: https://blog.ploeh.dk/2017/01/30/partial-application-is-depe...

[2]: https://blog.ploeh.dk/2017/01/27/dependency-injection-is-pas...


One 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`).


Agreed. 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?


>What about partial application of named parameters?

Builder pattern. In a language that accommodates it well, such as Elm, it would look like this:

    IconLib.downloadIcon
      |> IconLib.withSize 12
      |> IconLib.withColor "red"
      |> IconLib.withCssClass "dl-icon"
Allowing currying of any parameter like:

    standardDownloadIcon = IconLib.downloadIcon
      |> IconLib.withSize 12

    largeDownloadIcon = IconLib.downloadIcon
      |> IconLib.withSize 16


You 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.


Of 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.


I guess that's fair. What about Scala's _ syntax, though? Does it solve the problem?


> 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();


Currying 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.


What's the difference between currying and partial application/closures?


Currying 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.


The distinction sounds almost completely syntactic. I.e. foo(a, b) vs foo(a)(b). Is the former not just sugar for the latter?


> 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.


Ah that makes sense about default parameters and overloading. Hmm.


Not quite. In curried languages functions often only take in ONE argument in and return ONE value out. That simplification means the language can only accept the latter form (e.g. foo(a)(b)). Surprisingly this makes the language more powerful not less by unifying all functions to one shape.

For example this allows you to write methods that only take 1 input and 1 output type arg and handle all function cases with any amount of arguments. e.g. in Java/C# you typically have lots of types/anonymous class shapes to define different function shapes to represent lambdas/functions.

e.g. C# delegate syntax (Java has a more verbose way to do this last time I checked)

delegate T Action<T>(); delegate T Func<T>(); delegate R Func<T, R>(T firstArg); delegate R Func<T1, T2, R>(T1 firstArg, T2 secondArg);

Java has things like Consumer<>, Supplier<>, Function<> etc. If you want more than a https://docs.oracle.com/javase/8/docs/api/java/util/function... you typically have to define your own template that's not standard.

However in curried languages all use cases typically are handled by one form and the compiler does the mapping for you (e.g Haskell, F#, etc).

type Func<T, R> = T -> R

The compiler translates a method like foo(a, b) to foo(a)(b) and has certain compile tricks to avoid intermediate allocations if you're specifying all the args at once.

In practice it means writing a LOT less overloads to deal with all the different argument length cases defined and a lot less "Function" types to consider. Instead of an overload in Java for example with Function, BiFunction, etc or C# with Func<>, Func<_, _>, etc you just write the one or two and chain it.


So partial takes an N-ary function and gives an M-ary function, while currying always turns an N-ary function into N unary functions, that is the main distinction?


I 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.


> 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.


> 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.


I 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.


Ugh 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.


> 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).




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: