[HN Gopher] A case against currying
___________________________________________________________________
A case against currying
Author : emih
Score : 89 points
Date : 2026-03-22 13:03 UTC (9 hours ago)
(HTM) web link (emi-h.com)
(TXT) w3m dump (emi-h.com)
| messe wrote:
| What benefit does drawing a distinction between parameter list
| and single-parameter tuple style bring?
|
| I'm failing to see how they're not isomorphic.
| Pay08 wrote:
| The tuple style can't be curried (in Haskell).
| messe wrote:
| That's not what I'm talking about.
|
| The article draws a three way distinction between curried
| style (a la Haskell), tuples and parameter list.
|
| I'm talking about the distinction it claims exists between
| the latter two.
| disconcision wrote:
| all three are isomorphic. but in some languages if you
| define a function via something like `function myFun(x:
| Int, y: Bool) = ...` and also have some value `let a: (Int,
| Bool) = (1, true)` it doesn't mean you can call `myFun(a)`.
| because a parameter list is treated by the language as a
| different kind of construct than a tuple.
| antonvs wrote:
| A language which truly treats an argument list as a tuple
| can support this: args = (a, b, c)
| f args
|
| ...and that will have the effect of binding a, b, and c as
| arguments in the called function.
|
| In fact many "scripting" languages, like Javascript and
| Python, support something close to this using their array
| type. If you squint, you can see them as languages whose
| functions take a single argument that is equivalent to an
| array. At an internal implementation level this equivalence
| can be messy, though.
|
| Lower level languages like C and Rust tend not to support
| this.
| Pay08 wrote:
| Rust definitely should. C++s std::initializer_list is a
| great tool and you wouldn't need macros for variadic
| functions anymore.
| emih wrote:
| That's a fair point, they are all isomorphic.
|
| The distinction is mostly semantic so you could say they are
| the same. But I thought it makes sense to emphasize that the
| former is a feature of function types, and the latter is still
| technically single-parameter.
|
| I suppose one real difference is that you cannot feed a tuple
| into a parameter list function. Like:
|
| fn do_something(name: &str, age: u32) { ... }
|
| let person = ("Alice", 40);
|
| do_something(person); // doesn't compile
| Kambing wrote:
| They are isomorphic in the strong sense that their logical
| interpretations are identical. Applying Curry-Howard, a
| function type is an implication, so a curried function with
| type A -> B -> C is equivalent to an implication that says "If
| A, then if B, then C." Likewise, a tuple is a conjunction, so a
| non-curried function with type (A, B) -> C is equivalent to the
| logic statement (A /\ B) -> C, i.e., "If A and B then C." Both
| logical statements are equivalent, i.e., have the same truth
| tables.
|
| However, as the article outlines, there are differences (both
| positive and negative) to using functions with these types.
| Curried functions allow for partial application, leading to
| elegant definitions, e.g., in Haskell, we can define a function
| that sums over lists as sum = foldl (+) 0 where we leave out
| foldl's final list argument, giving us a function expecting a
| list that performs the behavior we expect. However, this style
| of programming can lead to weird games and unweildy code
| because of the positional nature of curried functions, e.g.,
| having to use function combinators such as Haskell's flip
| function (with type (A -> B -> C) -> B -> A -> C) to juggle
| arguments you do not want to fill to the end of the parameter
| list.
| messe wrote:
| Please see my other comment below, and maybe re-read the
| article. I'm not asking what the difference is between
| curried and non-curried. The article draws a three way
| distinction, while I'm asking why two of them should be
| considered distinct, and not the pair you're referring to.
| Kambing wrote:
| Apologies, I was focused on the usual pairing in this space
| and not the more subtle one you're talking about. As others
| have pointed out, there isn't really semantic a difference
| between the two. Both approaches to function parameters
| produce the same effect. The differences are purely in
| "implementation," either theoretically or in terms of
| systems-building.
|
| From a theoretical perspective, a tuple expresses the idea
| of "many things" and a multi-argument parameter list
| expresses the idea of both "many things" and "function
| arguments." Thus, from a cleanliness perspective for your
| definitions, you may want to separate the two, i.e.,
| require function have exactly one argument and then pass a
| tuple when multiple arguments are required. This
| theoretical cleanliness does result in concrete gains:
| writing down a formalism for single-argument functions is
| decidedly cleaner (in my opinion) than multi-argument
| functions and implementing a basic interpreter off of this
| formalism is, subsequently, easier.
|
| From a systems perspective, there is a clear downside in
| this space. If tuples exist on the heap (as they do for
| most functional languages), you induce a heap allocation
| when you want to pass multiple arguments! This pitfall is
| evident with the semi-common beginner's mistake with OCaml
| algebraic datatype definitions where the programmer
| inadvertently wraps the constructor type with parentheses,
| thereby specifying a constructor of one-argument that is a
| tuple instead of a multi-argument constructor (see
| https://stackoverflow.com/questions/67079629/is-a-
| multiple-a... for more details).
| recursivecaveat wrote:
| Probably just that having parameter-lists as a specific special
| feature makes them distinct from tuple types. So you may end up
| with packing/unpacking features to convert between them, and a
| function being generic over its number of parameters is
| distinct from it being generic over its input types. On the
| other hand you can more easily do stuff like named args or
| default values.
| layer8 wrote:
| The parameter list forces the individual arguments to be
| visible at the call site. You cannot separate the packaging of
| the argument list from invoking the function (barring special
| syntactic or library support by the language). It also affects
| how singleton tuples behave in your language.
|
| The article is about programmer ergonomics of a language. Two
| languages can have substantially different ergonomics even when
| there is a straightforward mapping between the two.
| naasking wrote:
| Presumably creating a different class for parameter lists
| allows you to extend it with operations that aren't natural to
| tuples, like named arguments.
| rocqua wrote:
| It's not that they are meaningfully different. It's just
| acknowledging if you really want currying, you can say 'why not
| just use a single parameter of tuple type'.
|
| Then there's an implication of 'sure, but that doesn't actually
| help much if it's not standar' and then it's not addressed
| further.
| recursivecaveat wrote:
| Currying was recently removed from Coalton: https://coalton-
| lang.github.io/20260312-coalton0p2/#fixed-ar...
| emih wrote:
| Thanks for sharing, interesting to see that people writing
| functional languages also experience the same issues in
| practice. And they give some reasons I didn't think about.
| leoc wrote:
| > 3. _Better type errors._ With currying, writing (f 1 2)
| instead of (f 1 2 3) silently produces a partial application.
| The compiler happily infers a function type like :s - > :t and
| moves on. The real error only surfaces later, when that
| unexpected function value finally clashes with an incompatible
| type, often far from the actual mistake. With fixed arity, a
| missing argument is caught right where it happens.
|
| 'Putting things' (multi-argument function calls, in this case)
| 'in-band doesn't make them go away, but it does successfully
| hide them from your tooling', part 422.
| brabel wrote:
| That's so cool. I already liked Coalton, and after this change
| I think it's definitely going to be even better. Can't wait to
| try it.
| Blikkentrekker wrote:
| > _Simplicity: Every function takes exactly one input and
| produces exactly one output. No exceptions. If you didn't care
| about the input or output, you used Unit, and we made special
| syntax for that._
|
| Seems like a disaster to use s-expressions for a language like
| that. I love s-expressions but they only make sense for
| variadic languages. The entire point of them is to quickly
| delimit how many arguments are passed.
|
| In say Haskell `f x y z` is the same thing as `(((f x) y) z)`.
| That is definitely not the case with s-expressions; braces
| don't delimit; they denote function application. It's like
| saying that `f(x,y,z)` being the same as `f(x)(y)(z)` which it
| really isn't. The point of s-expressions is that you often find
| yourself calling functions with many arguments that are
| themselves a result of a function application, at that point
| `foo(a)(g(a,b), h(x,y))` just becomes easier to parse as ((foo
| a) (g a b) (h x y))`.
| Pay08 wrote:
| I'm biased here since the easy currying is by far my favourite
| feature in Haskell (it always bothers me that I have to
| explicitly create a lamba in Lisps) but the arguments in the
| article don't convince me, what with the synctactic overhead for
| the "tuple style".
| lukev wrote:
| I'd got a step further and say that in business software, named
| parameters are preferable for all but the smallest functions.
|
| Using curried OR tuple arg lists requires remembering the name of
| an argument by its position. This saves room on the screen but is
| mental overhead.
|
| The fact is that arguments _do_ always have names anyway and you
| always have to know what they are.
| layer8 wrote:
| I want to agree, but there is the tension that in business
| code, what you pass as arguments is very often already named
| like the parameter, so having to indicate the parameter name in
| the call leads to a lot of redundancy. And if you're using
| domain types judiciously, the types are typically also
| different, hence (in a statically-typed language) there is
| already a reduced risk of passing the wrong parameter.
|
| Maybe there could be a rule that parameters have to be named
| only if their type doesn't already disambiguate them and if
| there isn't some concordance between the naming in the argument
| expression and the parameter, or something along those lines.
| But the ergonomics of that might be annoying as well.
| sestep wrote:
| This is an issue in Python but less so in languages like
| JavaScript that support "field name punning", where you pass
| named arguments via lightweight record construction syntax,
| and you don't need to duplicate a field name if it's the same
| as the local variable name you're using for that field's
| value.
| layer8 wrote:
| That forces you to name the variable identically to the
| parameter. For example, you may want to call your variable
| `loggedInUser` when the fact that the user is logged in is
| important for the code's logic, but then you can't pass it
| as-is for a field that is only called `user`. Having to
| name the parameter leads to routinely having to write `foo:
| blaFoo` because just `blaFoo` wouldn't match, or else to
| drop the informative `bla`. That's part of the tension I
| was referring to.
| twic wrote:
| OCaml has a neat little feature where it elides the parameter
| and variable name if they're the same: let
| warn_user ~message = ... (* the ~ makes this a named
| parameter *) let error = "fatal error!!" in
| warn_user ~message:error; (* different names, have to specify
| both *) let message = "fatal error!!" in
| warn_user ~message; (* same names, elided *)
|
| The elision doesn't always kick in, because sometimes you
| want the variable to have a different name, but in practice
| it kicks in a lot, and makes a real difference. In a way,
| cases when it doesn't kick in are also telling you something,
| because you're crossing some sort of context boundary where
| some value is called different things on either side.
| leoc wrote:
| Right. Currying as the default means of passing arguments in
| functional languages is a gimmick, a hack in the derogatory
| sense. It's low-level and anti-declarative.
| layer8 wrote:
| I completely agree. Giving the first parameter of a function
| special treatment only makes sense in a limited subset of cases,
| while forcing an artificial asymmetry in the general case that I
| find unergonomic.
| paldepind2 wrote:
| I completely agree with the points in this article and have come
| to the same conclusion after using languages that default to
| unary curried functions.
|
| > I'd also love to hear if you know any (dis)advantages of
| curried functions other than the ones mentioned.
|
| I think it fundamentally boils down to the curried style being
| _implicit_ partial application, whereas a syntax for partial
| application is _explicit_. And as if often the case, being
| explicit is clearer. If you see something like
| let f = foobinade a b
|
| in a curried language then you don't immediately know if `f` is
| the result of foobinading `a` and `b` or if `f` is `foobinade`
| partially applied to some of its arguments. Without currying
| you'd either write let f = foobinade(a, b)
|
| or let f = foobinade(a, b, $) // (using the
| syntax in the blog post)
|
| and now it's immediately explicitly clear which of the two cases
| we're in.
|
| This clarity not only helps humans, it also help compilers give
| better error messages. In a curried languages, if a function is
| mistakenly applied to too few arguments then the compiler can't
| always immediately detect the error. For instance, if `foobinate`
| takes 3 arguments, then `let f = foobinade a b` doesn't give rise
| to any errors, whereas a compiler can immediately detect the
| error in `let f = foobinade(a, b)`.
|
| A syntax for partial application offers the same practical
| benefits of currying without the downsides (albeit loosing some
| of the theoretical simplicity).
| munchler wrote:
| Well, I totally disagree with this. One of the main benefits of
| currying is the ability to chain function calls together. For
| example, in F# this is typically done with the |> operator:
| let result = input |> foobinade a b
| |> barbalyze c d
|
| Or, if we really want to name our partial function before
| applying it, we can use the >> operator instead:
| let f = foobinade a b >> barbalyze c d let result = f
| input
|
| Requiring an explicit "hole" for this defeats the purpose:
| let f = barbalyze(c, d, foobinade(a, b, $)) let result
| = f(input)
|
| Or, just as bad, you could give up on partial function
| application entirely and go with: let result
| = barbalyze(c, d, foobinade(a, b, input))
|
| Either way, I hope that gives everyone the same "ick" it gives
| me.
| emih wrote:
| You can still do this though: let result =
| (barbalyze(c, d, $) . foobinade(a, b, $)) input
|
| Or if you prefer left-to-right: let result =
| input |> foobinade(a, b, $) |> barbalyze(c,
| d, $)
|
| Maybe what isn't clear is that this hole operator would bind
| to the innermost function call, not the whole statement.
| raincole wrote:
| Wow, this convinced me. It's so obviously the right
| approach when you put it this way.
| twic wrote:
| Even better, this method lets you pipeline into a parameter
| which isn't the last one: let result =
| input |> add_prefix_and_suffix("They said '", $,
| "'!")
| raincole wrote:
| Yeah, especially in F#, a language that means to
| interpolate with .Net libraries (most not written with
| "data input at last" mindset.) now I'm quite surprised
| that F# doesn't have this feature.
| Smaug123 wrote:
| This is essentially how Mathematica does it: the sugar
| `Foo[x,#,z]&` is semantically the same as `Function[{y},
| Foo[x,y,z]]`. The `&` syntax essentially controls what hole
| belongs where.
| skybrian wrote:
| For pipelines in any language, putting one function call per
| line often works well. Naming the variables can help
| readability. It also makes using a debugger easier:
| let foos = foobinate(a, b, input) let bars =
| barbakize(c, d, foos)
|
| Other languages have method call syntax, which allows some
| chaining in a way that works well with autocomplete.
| RHSeeger wrote:
| > Naming the variables can help readability
|
| It can, or it can't; depending on the situation. Sometimes
| it just adds weight to the mental model (because now
| there's another variable in scope).
| skybrian wrote:
| Sure, I like chained method calls too, for simple things.
| But it gets ridiculous sometimes where people write a
| ten-stage pipeline in a single expression and then call
| that "readable."
| riwsky wrote:
| The functional programming take is that "the result of
| foobinade-ing an and b" IS "foobinade applied to two of its
| arguments". The application is not some syntactic pun or
| homonym that can refer to two different meanings--those are the
| same meaning.
| AnimalMuppet wrote:
| Let us postulate two functions. One is named foobinade, and
| it takes three arguments. The other is named foobinadd, and
| it only takes two arguments. (Yes, I know, shoot anybody who
| actually names things that way.)
|
| When someone writes f = foobinade a b g
| = foobinadd c d
|
| there is no confusion to the compiler. The problem is the
| _reader_. Unless you have the signatures of foobinade and
| foobinadd memorized, you have no way to tell that f is a
| curried function and g is an actual result.
|
| Whereas with explicit syntax, the parentheses say what the
| author thinks they're doing, and the compiler will yell at
| them if they get it wrong.
| zahlman wrote:
| > Unless you have the signatures of foobinade and foobinadd
| memorized, you have no way to tell that f is a curried
| function and g is an actual result.
|
| Yes, but the exact FP idea here is that this distinction is
| meaningless; that curried functions _are_ "actual results".
| Or rather, you never have a result that _isn 't_ a
| function; `0` and `lambda: 0` (in Python syntax) are the
| same thing.
|
| It does, of course, turn out that for many people this
| isn't a natural way of thinking about things.
| AnimalMuppet wrote:
| Fine, it's a regular type. It's still _not the type I
| think it is_. If it 's an Int -> Int when I think it's an
| Int, that's still a problem, no matter how much Int ->
| Int is an "actual result".
| kccqzy wrote:
| Come on, just write let f :: Int =
| foobinade a b
|
| And the compiler immediately tells you that you are
| wrong: your type annotation does not unify with
| compiler's inferred type.
|
| And if you think this is verbose, well many traditional
| imperative languages like C have no type deduction and
| you will need to provide a type for every variable
| anyways.
| AnimalMuppet wrote:
| I spent the last three years on the receiving end of mass
| quantities of code written by people who knew what they
| were writing but didn't do an adequate job of communicate
| it to readers who didn't already know everything.
|
| What you say is true. And it works, if you're the author
| and are having trouble keeping it all straight. It
| doesn't work if the author didn't do it and you are the
| reader, though.
|
| And that's the more common case, for two reasons. First,
| code is read more often than it's written. Second, when
| you're the author, you probably already have it in your
| head how many parameters foobinade takes when you call
| it, but when you're the reader, you have to go consult
| the definition to find out.
|
| But if I was willing to do it, I could go through and
| annotate the variables like that, and have the compiler
| tell me everything I got wrong. It would be tedious, but
| I could do it.
| raincole wrote:
| > Yes, but the exact FP idea here is that this
| distinction is meaningless; that curried functions are
| "actual results".
|
| Everyone knows that. At least everyone who would click a
| post titled "A case against currying." The article's
| author clearly knows that too.
|
| That's not the point. The point is that this distinction
| is very meaningful in practice, as many functions are
| only meant to be used in one way. It's extremely rare
| that you need to (printf "%d %d" foo). The extra freedom
| provided by currying is useful, but it should be opt-in.
|
| Just because two things are _fundamentally_ equivalent,
| it doesn 't mean it's useless to distinguish them.
| Mathematics is the art of giving the same name to
| different things; and engineering is the art of giving
| different names to the same thing depending on the
| context.
| kccqzy wrote:
| > It's extremely rare that
|
| Not when a language embraces currying fully and then you
| find that it's used all the fucking time.
|
| It's really simple as that: a language makes the currying
| syntax easy, and programmers use it all the time; a
| language disallows currying or makes the currying syntax
| unwieldy, and programmers avoid it.
| momentoftop wrote:
| > It's extremely rare that you need to (printf "%d %d"
| foo)
|
| I write stuff like `map (printf "%d %d" m) ns` all the
| time. I daresay I even do the map as a partial
| application, so double currying.
| gf000 wrote:
| But arguably your intent would be much more clear with
| something like `map (printf "%d %d" m _) ns` or a lambda.
|
| I don't think parent is saying that partial application
| is bad, far from it. But to a reader it _is_ valuable
| information whether it 's partial or full application.
| jstanley wrote:
| If 0 and a function that always returns 0 are the same
| thing, does that make `lambda: lambda: 0` also the same?
| I suppose it must do, otherwise `0` and `lambda: 0` were
| not truly the same.
| fn-mote wrote:
| Another way to make the point: when you write 0, which do
| you mean?
|
| In a pure language like Haskell, 0-ary functions <==>
| constants
| skywhopper wrote:
| It's not at all clear or the same to the new reader of
| the code.
| jwarden wrote:
| Here's an article I wrote a while ago about a hypothetical
| language feature I call "folded application", that makes
| parameter-list style and folded style equivalent.
|
| https://jonathanwarden.com/implicit-currying-and-folded-appl...
| 01HNNWZ0MV43FF wrote:
| I've never ever run into this. I haven't seen currying or partial
| application since college. Am I the imperative Blub programmer,
| lol?
| mkprc wrote:
| Prior to this article, I didn't think of currying as being
| something a person could be "for" or "against." It just is. The
| fact that a function of multiple inputs can be equivalently
| thought of as a function of a tuple can be equivalently thought
| of as a composite of single-input functions that return functions
| is about cognition, and understanding structure, not code syntax.
| kevincox wrote:
| But it is about code syntax. Languages like Haskell make it
| part of the language by only supporting single-argument
| functions. So currying is the default behaviour for
| programmers.
|
| I think you are focusing on the theoretical aspect of partial
| application and missing the actual argument of the article
| which having it be the default, implicit way of defining and
| calling functions isn't a good programming interface.
| bbkane wrote:
| Similar to how lambda calculus "just is" (and it's very
| elegant and useful for math proofs), but nobody writes non-
| trivial programs in it...
| tromp wrote:
| Make that almost nobody.
|
| I wrote a non-trivial lambda program [1] which enumerates
| proofs in the Calculus of Constructions to demonstrate [2]
| that BBl(1850) > Loader's Number.
|
| [1] https://github.com/tromp/AIT/blob/master/fast_growing_a
| nd_co...
|
| [2]
| https://codegolf.stackexchange.com/questions/176966/golf-
| a-n...
| AnimalMuppet wrote:
| I'm a programmer, not a computer scientist. The equivalence is
| a computer science thing. They are logically equivalent in
| theoretical computer science. Fine.
|
| They are not equally easy for me to use when I'm writing a
| program. So from a software engineering perspective, they are
| very much not the same.
| titzer wrote:
| I agree with this article. Tuples nicely unified multiple return
| values and multiple parameters. FWIW Scala and Virgil both
| support the _ syntax for the placeholder in a partial
| application. def add(x: int, y: int) -> int {
| return x + y; } def add3 = add(_, 3);
|
| Or more simply, reusing some built-in functions:
| def add3 = int.+(_, 3);
| ackfoobar wrote:
| As noted in the article:
|
| > This feature does have some limitations, for instance when we
| have multiple nested function calls, but in those cases an
| explicit lambda expression is always still possible.
|
| I've also complained about that a while ago
| https://news.ycombinator.com/item?id=35707689
|
| ---
|
| The solution is to delimit the level of expression the
| underscore (or dollar sign suggested in the article) belongs
| to. In Kotlin they use braces and `it`. {
| add(it, 3) } // Kotiln add(_, 3) // Scala
|
| Then modifying the "hole in the expression" is easy. Suppose we
| want to subtract the first argument by 2 before passing that to
| `add`: { add(subtract(it, 2), 3) } // Kotlin
| // add(subtract(_, 2), 3) // no, this means adding 3 to the
| function `add(subtract(_, 2)` x => { add(subtract(x,
| 2), 3) } // Scala
| titzer wrote:
| I think I like the explicit lambda better; I prefer to be
| judicious with syntactic sugar and special variable names.
| fun x => add(subtract(x, 2), 3) // Virgil
| ackfoobar wrote:
| Coming from Scala to Kotlin, this is what I thought as
| well. Seeing `it` felt very wrong, then I got used to it.
| bbkane wrote:
| The Roc devs came to a similar conclusion: https://www.roc-
| lang.org/faq#curried-functions
|
| (Side note: if you're reading this Roc devs, could you add a
| table of contents?)
| jstrieb wrote:
| I like currying because it's fun and cool, but found myself
| nodding along throughout the whole article. I've taken for
| granted that declaring and using curried functions with nice
| associativity (i.e., avoiding lots of parentheses) is as
| ergonomic as partial application syntax gets, but I'm glad to
| have that assumption challenged.
|
| The "hole" syntax for partial application with dollar signs is a
| really creative alternative that seems much nicer. Does anyone
| know of any languages that actually do it that way? I'd love to
| try it out and see if it's actually nicer in practice.
| emih wrote:
| Glad to hear the article did what I meant for it to do :)
|
| And yes, another comment mentioned that Scala supports this
| syntax!
| rocqua wrote:
| Someone else in the comments mentioned that scala does this
| with _ as the placeholder.
| runevault wrote:
| Clojure CL as well have macros that let you thread results from
| call to call, but you could argue that's cheating because of
| how flexible Lisp syntax is.
| hencq wrote:
| Clojure also has the anonymous function syntax with #(foo a b
| %) where you essentially get exactly this hole functionality
| (but with % instead of $). Additionally there's partial that
| does partial application, so you could also do (partial foo a
| b).
| zyxzevn wrote:
| With a language like Forth, you know that you can use a stack for
| data and apply functions on that data. With currying it you put
| functions on a stack instead. This makes it weird. But you also
| obscure the dataflow.
|
| With the most successful functional programing language Excel,
| the dataflow is fully exposed. Which makes it easy.
|
| Certain functional programming languages prefer the passing of
| just one data-item from one function to the next. One parameter
| in and one parameter out. And for this to work with more values,
| it needs to use functions as an output. It is unnecessary
| cognitive burden. And APL programmers would love it.
|
| Let's make an apple pie as an example. You give the apple and
| butter and flour to the cook. The cursed curry version would be
| "use knife for cutting, add cutting board, add apple, stand near
| table, use hand. Bowl, add table, put, flour, mix, cut, knife
| butter, mixer, put, press, shape, cut_apple." etc..
| codethief wrote:
| I've long been thinking the same thing. In many fields of
| mathematics the placeholder $ from the OP is often written *,
| i.e. partial function application is written as f(a, b, *). I've
| always found it weird that most functional languages,
| particularly heavily math-inspired ones like Haskell, deviate
| from that. Yes, there are isomorphisms left and right but at the
| end of the day you have to settle on one category and one syntax.
| A function f: A x B -> C is simply not the same thing as a
| function f: A -> B -> C. Stop treating it like it is.
| vq wrote:
| One "feature of currying" in Haskell that isn't mentioned in the
| fine article is that parts of the function may not be dependent
| on the last argument(s) and only needs to be evaluated once over
| many application of the last argument(s) which can be very useful
| when partially applied functions are passed to higher-order
| functions.
|
| Functions can be done explicitly written to do this or it can be
| achieved through compiler optimisation.
| emih wrote:
| That's a very good point, I never thought really about how this
| relates to the execution model & graph reduction and such. Do
| you have an example of a function where this can make a
| difference? I might add something to the article about it.
|
| It's also a question of whether this is exclusive to a curried
| definition or if such an optimization may also apply to partial
| application with a special operator like in the article. I
| think it could, but the compiler might need to do some extra
| work?
| vq wrote:
| One slightly contrived example would be if you had a function
| that returned the point of a set closest to another given
| point.
|
| getClosest :: Set Point -> Point -> Point
|
| You could imagine getClosest build a quadtree internally and
| that tree wouldn't depend on the second argument. I say
| slightly contrived because I would probably prefer to make
| the tree explicit if this was important.
|
| Another example would be if you were wrapping a C-library but
| were exposing a pure interface. Say you had to create some
| object and lock a mutex for the first argument but the second
| was safe. If this was a function intended to be passed to
| higher-order functions then you might avoid a lot of
| unnecessary lock contention.
|
| You may be able to achieve something like this with
| optimisations of your explicit syntax, but argument order is
| relevant for this. I don't immediately see how it would be
| achieved without compiling a function for every permutation
| of the arguments.
| emih wrote:
| Those are nice examples, thanks.
|
| I was imagining you might achieve this optimization by
| inlining the function. So if you have
| getClosest(points, p) = findInTree(buildTree(points), p)
|
| And call it like myPoints = [...] map
| (getClosest(myPoints, $)) myPoints
|
| Then the compiler might unfold the definition of getClosest
| and give you map (\p ->
| findInTree(buildTree(myPoints), p)) myPoints
|
| Where it then notices the first part does not depend on p,
| and rewrite this to let tree =
| buildTree(myPoints) in map (\p -> findInTree(tree, p))
| myPoints
|
| Again, pretty contrived example. But maybe it could work.
| vq wrote:
| I didn't consider inlining but I believe you're correct,
| you could regain the optimisation for this example since
| the function is non-recursive and the application is
| shallow. The GHC optimisation I had in mind is like the
| opposite of inlining, it factors out a common part out of
| a lambda expression that doesn't depend on the variable.
|
| I don't believe inlining can take you to the exact same
| place though. Thinking about explicit INLINE pragmas, I
| envision that if you were to implement your partial
| function application sugar you would have to decide
| whether the output of your sugar is marked INLINE and
| either way you choose would be a compromise, right? The
| compromise with Haskell and curried functions today is
| that the programmer has to consider the order of
| arguments, it only works in one direction but on the
| other hand the optimisation is very dependable.
| twic wrote:
| I think we need to see a few non-contrived examples,
| because i think in every case where you might take
| advantage of currying like this, you actually want to make
| it explicit, as you say.
|
| The flip side of your example is that people see a function
| signature like getClosest, and think it's fine to call it
| many times with a set and a point, and now you're building
| a fresh quadtree on each call. Making the staging explicit
| steers them away from this.
| addaon wrote:
| Consider a function like 'match regex str'. While non-
| lazy languages may offer an alternate API for pre-
| compiling the regex to speed up matching, partial
| evaluation makes that unnecessary.
| 12_throw_away wrote:
| > and now you're building a fresh quadtree on each call
| [...] Making the staging explicit steers them away from
| this.
|
| Irrespective of currying, this is a really interesting
| point - that the structure of an API should reflect its
| runtime resource requirements.
| taolson wrote:
| An example where this is useful is to help inline otherwise
| recursive functions, by writing the function to take some
| useful parameters first, then return a recursive function
| which takes the remaining parameters. This allows the
| function to be partially in-lined, resulting in better
| performance due to the specialization on the first
| parameters. For example, foldr:
|
| foldr f z = go where go [] = z
| go (x : xs) = f x (go xs)
|
| when called with (+) and 0 can be inlined to
|
| go xs = case xs of [] -> 0 (x
| : xs) = x + go xs
|
| which doesn't have to create a closure to pass around the
| function and zero value, and can subsequently inline (+),
| etc.
| ackfoobar wrote:
| > explicitly written to do this
|
| In that case I want the signature of "this function pre-
| computes, then returns another function" and "this function
| takes two arguments" to be different, to show intent.
|
| > achieved through compiler optimisation
|
| Haskell is different in that its evaluation ordering allows
| this. But in strict evaluation languages, this is much harder,
| or even forbidden by language semantics.
|
| Here's what Yaron Minsky (an OCaml guy) has to say:
|
| > starting from scratch, I'd avoid partial application as the
| default way of building multi-argument functions.
|
| https://discuss.ocaml.org/t/reason-general-function-syntax-d...
| kubb wrote:
| The article lists two arguments against Currying:
| 1) "performance is a bit of a concern" 2) "curried function
| types have a weird shape"
|
| 2 is followed by single example of how it doesn't work the way
| the author would expect it to in Haskell.
|
| It's not a strong case in my opinion. Dismissed.
| skybrian wrote:
| There are good ideas in functional languages that other languages
| have borrowed, but there are bad ideas too: currying, function
| call syntax without parentheses, Hindley-Milner type inference,
| and laziness by default (Haskell) are experiments that new
| languages shouldn't copy.
| raincole wrote:
| I believe one of the main reasons that F# hasn't never really
| taken off is that Microsoft isn't afraid to borrow the good
| parts of F# to C#. (They really should've ported discriminated
| unions though)
| runevault wrote:
| Currently DUs are slated for the next version of c# releasing
| end of this year. However last I knew they only come boxed
| which at least to me partly defeats the point of having them
| (being able to have multiple types inline because of the way
| they share memory and only have a single size based on
| compiler optimizations).
| kajaktum wrote:
| I feel like not having currying means your language becomes
| semantically more complicated because where does lambdas come
| from?
| hutao wrote:
| One language that uses the tuple argument convention described in
| the article is Standard ML. In Standard ML, like OCaml and
| Haskell, all functions take exactly one argument. However, while
| OCaml and Haskell prefer to curry the arguments, Standard ML does
| not.
|
| There is one situation, however, where Standard ML prefers
| currying: higher-order functions. To take one example, the type
| signature of `map` (for mapping over lists) is `val map : ('a ->
| 'b) -> 'a list -> 'b list`. Because the signature is given in
| this way, one can "stage" the higher-order function argument and
| represent the function "increment all elements in the list" as
| `map (fn n => n + 1)`.
|
| That being said, because of the value restriction [0], currying
| is less powerful because variables defined using partial
| application cannot be used polymorphically.
|
| [0] http://mlton.org/ValueRestriction
| emih wrote:
| I didn't know Standard ML, that's interesting.
|
| And yeah I think this is the way to go. For higher-order
| functions like map it feels too elegant not to write it in a
| curried style.
| twic wrote:
| I couldn't agree more. Having spent a lot of time with a language
| with currying like this recently, it seems very obviously a
| misfeature.
|
| 1. Looking at a function call, you can't tell if it's returning
| data, or a function from some unknown number of arguments to
| data, without carefully examining both its declaration and its
| call site
|
| 2. Writing a function call, you can accidentally get a function
| rather than data if you leave off an argument; coupled with
| pervasive type inference, this can lead to some really tiresome
| compiler errors
|
| 3. Functions which return functions look just like functions
| which take more arguments and return data (card-carrying
| functional programmers might argue these are really the same
| thing, but semantically, they aren't at all - in what sense is
| make_string_comparator_for_locale "really" a function which takes
| a locale and a string and returns a function from string to
| ordering?)
|
| 3a. Because of point 3, our codebase has a trivial wrapper to put
| round functions when your function actually returns a function
| (so make_string_comparator_for_locale has type like Locale ->
| Function<string -> string -> order>), so now if you actually want
| to return a function, there's boilerplate at the return and call
| sites that wouldn't be there in a less 'concise' language!
|
| I think programming languages have a tendency to pick up cute
| features that give you a little dopamine kick when you use them,
| but that aren't actually good for the health of a substantial
| codebase. I think academic and hobby languages, and so functional
| languages, are particularly prone to this. I think implicit
| currying is one of these features.
| tikhonj wrote:
| > _in what sense is make_string_comparator_for_locale "really"
| a function which takes a locale and a string and returns a
| function from string to ordering?_
|
| In the sense that "make_string_comparator" _is not a useful
| concept_. Being able to make a "string comparator" is
| inherently a function of being able to compare strings, and
| carving out a bespoke concept for some variation of this
| universal idea adds complexity that is neither necessary nor
| particularly useful. At the extreme, that's how you end up with
| Enterprise-style OO codebases full of useless nouns like
| "FooAdapter" and "BarFactory".
|
| The alternative is to have a consistent, systematic way to turn
| verbs into nouns. In English we have gerunds. I don't have to
| say "the sport where you ski" and "the activity where you
| write", I can just say "skiing" and "writing". In functional
| programming we have lambdas. On top of that, curried functions
| are just a sort of convenient contraction to make the common
| case smoother. And hey, maybe the _contraction_ isn 't worth
| the learning curve or usability edge-cases, but the function
| it's serving is still important!
|
| > _Because of point 3, our codebase has a trivial wrapper to
| put round functions when your function actually returns a
| function_
|
| That seems either completely self-inflicted, or a limitation of
| whatever language you're using. I've worked on a number of
| codebases in Haskell, OCaml and a couple of Lisps, and I have
| never seen or wanted anything remotely like this.
| marcosdumay wrote:
| > I think programming languages have a tendency to pick up cute
| features that give you a little dopamine kick when you use
| them, but that aren't actually good for the health of a
| substantial codebase.
|
| That's not the case with Haskell.
|
| Haskell has a tendency to pick up features that have deep
| theoretical reasoning and "mathematical beauty". Of course,
| that doesn't always correlate with codebase health very well
| either, and there's a segment of the community that is very
| vocal about dropping features because of that.
|
| Anyway, the case here is that a superficial kind of
| mathematical beauty seems to conflict with a deeper case of it.
| Blikkentrekker wrote:
| I always felt Monads were an utterly disgusting hack that was
| otherwise quite practical though. It didn't feel like
| mathematical beauty at all to me but like a hack to fool to
| the optimizer to not sequence out of events.
| jhhh wrote:
| A benefit to using the currying style is that you can do work in
| the intermediate steps and use that later. It is not simply a
| 'cool' way to define functions. Imagine a logging framework:
| (log configuration identifier level format-string arg0 arg1 ...
| argN)
|
| After each partial application step you can do more and more work
| narrowing the scope of what you return from subsequent functions.
| ;; Preprocessing the configuration is possible ;; Imagine
| all logging is turned off, now you can return a noop
| (partial log conf) ;; You can look up the identifier in the
| configuration to determine what the logger function should look
| like (partial log conf id) ;; You could return a noop
| function if the level is not enabled for the particular id
| (partial log config id level) ;; Pre-parsing the format
| string is now possible (partial log conf id level "%time -
| %id")
|
| In many codebases I've seen a large amount of code is literally
| just to emulate this process with multiple classes, where you're
| performing work and then caching it somewhere. In simpler cases
| you can consolidate all of that in a function call and use
| partial application. Without some heroic work by the compiler you
| simply cannot do that in an imperative style.
| instig007 wrote:
| if you don't find currying essential you haven't done pointfree
| enough. If you haven't done pointfree enough you haven't picked
| equational reasoning yet, and it's the thing that holds you back
| in your ability to read abstractions easily, which in turn guides
| your arguments on clarity.
| Isognoviastoma wrote:
| > curried functions often don't compose nicely
|
| Same for imperative languages with "parameter list" style. In
| python, with
|
| def f(a, b): return c, d
|
| def g(k, l): return m, n
|
| you can't do
|
| f(g(1,2))
|
| but have to use
|
| f(*g(1,2))
|
| what is analogical to uncurry, but operate on value rather than
| function.
|
| TBH I can't name a language where such f(g(1,2)) would work.
| shawn_w wrote:
| perl, though that uses lists rather than multiple value or
| fixed-size tuples: #!/usr/bin/env perl
| use v5.36; sub f($a, $b) { return ($a+1,
| $b+1); } sub g($k, $l) { return
| ($k+1, $l+1); } say for f(g(1,2));
|
| prints out 3 4
| calf wrote:
| What's the steelman argument though? Why do languages like
| Haskell have currying? I feel like that is not set out clearly in
| the argument.
| emih wrote:
| Mathematically it's quite pretty, and it gives you elegant
| partial application for free (at least if you want to partially
| apply the first N arguments).
| mrkeen wrote:
| I have a web backend with a top-level server that looks
| something like: ... :<|>
| collectionsServer compactor env registry :<|> queryServer
| qp sp struc registry warcFileReader logger :<|>
| indexationServer fetcher indexer ...
|
| I.e. a request coming into the top-level server will go into
| the collections server _or_ the query server _or_ the
| indexation server. Each server is further broken down
| (collections has 4 routes, query has 4 routes, indexation has 5
| routes.)
|
| So lets try making the the arguments of just the collections
| server explicit. (It will take me too long to try to do them
| all.)
|
| You can 'list' collections, 'delete' a collection, merge
| collectionA into collectionB, or get the directory where the
| collections live. So the input (the lambda term(s) we're trying
| to make explicit) can be () _or_ (collectionName) _or_
| (collectionNameA, collectionNameB) _or_ ().
|
| In order to put these lambda terms explicitly into the source
| code, we need to add four places to put them, by replacing
| collectionsServer with the routes that it serves:
| ... :<|> ( listCollections registry
| :<|> (\collectionName -> deleteCollection env registry
| collectionName) :<|> (\collectionName1
| collectionName2 -> mergeInto compactor collectionName1
| collectionName2) :<|> getCollectionDir env )
| :<|> queryServer qp sp struc registry warcFileReader logger
| :<|> indexationServer fetcher indexer ...
|
| And now you know what explicit lambda terms collectionsServer
| takes!
| gavinhoward wrote:
| Okay, but if you combine the curried and tuple styles, and add a
| dash of runtime function pointers, you can solve the expression
| problem. [1]
|
| [1]: https://gavinhoward.com/2025/04/how-i-solved-the-
| expression-...
| drathier wrote:
| 1. Such bad examples :( Tuples are data types you have to
| destruct, in every language. Somebody please show me a language
| where this doesn't require a tuple-to-function-argument
| translation: sayHi name age = "Hi I'm " ++ name
| ++ " and I'm " ++ show age people = [("Alice", 70), ("Bob",
| 30), ("Charlotte", 40)] -- ERROR: sayHi is String -> Int ->
| String, a person is (String, Int) conversation =
| intercalate "\n" (map sayHi people)
|
| In python you have `*people` to destruct the tuple into separate
| arguments, or pattern matching. In C-languages you have structs
| you have to destruct.
|
| 2. And performance, you'd think a slow-down affecting every
| single function call would be high-up on the optimization wish
| list, right? That's why it's implemented in basically every
| compiler, including non-fp compilers. Here's GHC authors in 2004
| declaring that obviously the optimization is in "any decent
| compiler". https://simonmar.github.io/bib/papers/eval-apply.pdf
|
| 3. Type errors, the only place where currying is actually bad, is
| not even mentioned directly. Accidentally passing a different
| number of arguments compared to what you expected will result in
| a compiler error.
|
| Some very powerful and generic languages will happily support
| lots of weird code you throw at them instead of erroring out.
| Others will errors out on things you'd expect them to handle just
| fine.
|
| Here's Haskell supporting something most people would never want
| to use, giving it a proper type, and causing a confusing type
| error in any surrounding code when you leave out a parentheis
| around `+`: foldl (+) 0 [1,2,3] :: Num a => a
| foldl + 0 [1,2,3] :: (Foldable t, Num a1, Num ((b -> a2
| -> b) -> b -> t a2 -> b), Num ([a1] -> (b -> a2 -> b)
| -> b -> t a2 -> b)) => (b -> a2 -> b) -> b -> t a2 ->
| b
|
| Is it bad that it has figured out that you (apparently) wanted to
| add things of type `(b -> a2 -> b) -> b -> t a2 -> b` as if they
| were numbers, and done what you told it to do? Drop it into any
| gpt of choice and it'll find the mistake for you right away.
| Blikkentrekker wrote:
| In SML I believe. I never used SML but from how I understand it
| in ML all functions technically take one argument, which may be
| a tuple. In Haskell and Ocaml, all functions technically take
| one argument and just return a function that takes one argument
| again.
|
| I never understood why the latter was so popular. Just for
| automatic implitic partial application which honestly should
| just have explicit syntax. In Scheme one simply uses the `(cut
| f x y)` operator which does a partial application and returns a
| function that consumes the remaining arguments which is far
| more explicit. But since Scheme is dynamically typed implicit
| partial application would be a disaster but it's not like in
| OCaml and Haskell the error messages at times can't be
| confusing.
|
| I don't get simulating it with tuples either to be honest.
| Nothing wrong with just letting functions take multiple
| arguments and that's it. In Rust they oddly take multiple
| arguments as expect, but they can return tuples to simulate
| returning multiple arguments whereas in Scheme they just return
| multiple arguments. There's a difference between returning one
| argument which is a tuple of multiple arguments, and actually
| returning multiple arguments.
|
| I think automatic implicit partial application, like almost
| anything "implicit" is bad. But in Haskell or Ocaml or even
| Rust it has to be a syntactic macro, it can't just be a normal
| function because no easy variadic functions which to be fair is
| incredibly difficult without dynamic typing and in practice
| just passing some kind of sequence is what you really want.
| shawn_w wrote:
| A bunch of Scheme implementations define little-known syntax for
| partial application[0] that lets you put limits on how many
| arguments have to be provided at each application step. Using the
| article's add example: (define (((add x) y) z) (+
| x y z)) (define add1 (add 1)) (define add3 (add1 2))
| (add3 3) ; => 6
|
| it gets tedious with lots of single-argument cases like the
| above, but in cases where you know you're going be calling a
| function a lot with, say, the first three arguments always the
| same and the fourth varying, it can be cleaner than a function of
| three arguments that returns an anonymous lambda of one argument.
| (define ((foo a b c) d) (do-stuff)) (for-each (foo
| 1 2 3) '(x y z))
|
| vs (define (foo a b c) (lambda (d) (do-
| stuff))) (for-each (foo 1 2 3) '(x y z))
|
| There's also a commonly supported placeholder syntax[1]:
| (define inc (cut + 1 <>)) (inc 2) ; => 3 (define
| (foo a b c d) (do-stuff)) (for-each (cut foo 1 2 3 <>)
| '(x y z))
|
| And assorted ways to define or adapt functions to make fully
| curried ones when desired. I like the "make it easy to do
| something complicated or esoteric when needed, but don't make it
| the default to avoid confusion" approach.
|
| [0]: https://srfi.schemers.org/srfi-219/srfi-219.html
|
| [1]: https://srfi.schemers.org/srfi-26/srfi-26.html
| dragonwriter wrote:
| A case for currying:
|
| In languages in which every function is unary but there is a
| convenience syntax for writing "multiargument" functions that
| produces curried functions, so that the type functions of type "a
| -> b -> c" can be written as if their type was "a b -> c", but
| which also have tuples such that "multiargument" functions could
| equally conveniently be written as having type "(a, b) -> c", and
| where the syntax for calling each type of function is equally
| straightforward in situations that don't require "partial
| application" (where the curried form has a natural added
| utility), people overwhelming use the syntax that produces
| curried functions.
|
| People only predominantly use uncurried multiargument functions
| in languages which make writing and/or calling curried functions
| significant syntactic overhead.
| mrkeen wrote:
| If you're looking for this argument in a language closer to home,
| it's basically the opposite of your IDE's style guide:
|
| If I write this Java: pair.map((a, b) ->
| Foo.merge(a, b));
|
| My IDE flashes up with _Lambda can be replaced with method
| reference_ and gives me pair.map(Foo::merge);
|
| (TFA does not seem to be arguing against the idea of partial-
| function-application itself, as much as he wants languages to be
| explicit about using the full lambda terms and function-call-
| parentheses.)
| et1337 wrote:
| I also think there's an interesting effect when cool functional
| language features like currying and closures are adopted by
| imperative languages. They make it way too easy to create state
| in a way that makes you FEEL like you're writing beautiful pure
| functions. Of course, in a functional language everything IS pure
| and this is just how things work. But in an imperative language
| you can trick yourself into thinking you've gotten away with
| something. At one point I stored practically all state in local
| variables captured by closures. It was a dark time.
| yibers wrote:
| I'm actually fascinated by what you wrote. Why was it a dark
| time?
| et1337 wrote:
| No encapsulation... huge functions with tons of local
| variables shared between closures... essentially global state
| in practice. I think ant the time, objects with member
| variables felt "heavy" and local variables felt "light". But
| the fact that they were so lightweight just gave me more
| opportunities to squirrel away state into random places with
| no structure around it. It really wasn't all that horrific,
| and it helped me ship something quickly, but it wasn't
| maintainable. These days I think the "heavy boilerplate" of
| grouping stuff into structs and objects forces me to slow
| down and think a bit harder about whether I really want to
| enshrine a new piece of state into the app's data model. Most
| of the time I don't.
| ifh-hn wrote:
| I didn't know what currying was before I read the article, and
| having read some of it before I gave up, I think it's something
| to do with functions.
___________________________________________________________________
(page generated 2026-03-22 23:01 UTC)