Need help?
<- Back

Comments (110)

  • paldepind2
    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).
  • jhhh
    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.
  • recursivecaveat
    Currying was recently removed from Coalton: https://coalton-lang.github.io/20260312-coalton0p2/#fixed-ar...
  • vq
    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.
  • Pay08
    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".
  • twic
    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 site2. 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 errors3. 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.
  • hutao
    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
  • lukev
    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.
  • dragonwriter
    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.
  • et1337
    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.
  • shawn_w
    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
  • mrkeen
    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.)
  • titzer
    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);
  • skybrian
    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.
  • ifh-hn
    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.
  • jstrieb
    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.
  • drathier
    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.pdf3. 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.
  • layer8
    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.
  • zyxzevn
    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..
  • jwarden
    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...
  • gavinhoward
    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-...
  • bbkane
    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?)
  • Isognoviastoma
    > curried functions often don't compose nicelySame for imperative languages with "parameter list" style. In python, withdef f(a, b): return c, ddef g(k, l): return m, nyou can't dof(g(1,2))but have to usef(*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.
  • kubb
    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.
  • codethief
    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 × B -> C is simply not the same thing as a function f: A -> B -> C. Stop treating it like it is.
  • kajaktum
    I feel like not having currying means your language becomes semantically more complicated because where does lambdas come from?
  • calf
    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.
  • 01HNNWZ0MV43FF
    I've never ever run into this. I haven't seen currying or partial application since college. Am I the imperative Blub programmer, lol?
  • messe
    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.
  • instig007
    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.
  • talkingtab
    [flagged]
  • leoc
    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.
  • mkprc
    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.