A case against currying
Recorded: March 22, 2026, 10 p.m.
| Original | Summarized |
A Case Against Currying - emih.com emi-h.com Software Projects Music Articles Resources Toggle theme A Case Against Currying Curried functions are probably one of the first new things you come across if you go from an imperative language to add x y z = x + y + z -- you can apply left-to-right: We make the arrow -> right-associative, so we can write Int -> Int -> Int -> Int. I want to argue that when you define functions in this style, although it is nice and elegant, there are also some There are roughly three different "styles" that programming languages offer for writing multi-parameter function fn f(p1: P1, p2: P2, p3: P3) -> R { ... } // definition Another form is the "curried" style, offered in pure functional languages like Haskell: f p1 p2 p3 = ... -- definition Finally, there is the "tuple" style. It looks similar to the parameter list style, but the multiple parameters are f(p1, p2, p3) = ... -- definition Note that some imperative languages also offer these functional styles, but it gets a bit unwieldy. For instance, const f = p1 => p2 => p3 => ...; We can also do the tuple style in Rust: fn f((p1, p2, p3): (P1, P2, P3)) -> R { ... } Ultimately, even though these styles have different practical use cases, they are equivalent in theory: the types When you ask the internet why we do curried functions, the main response is add' = add 1 add'' = add' 2 add'' 3 -- returns 6 This is especially nice when we have higher-level functions like map and fold. For length = foldr (+) 0 . map (const 1) However, it is often wrongly assumed that the option of doing partial application is a special property of curried add(x, y, z) = x + y + z add' = let x = 1 in \(y, z) -> add(x, y, z) "But," I hear you say, "this clearly looks horrible." Or maybe not, I don't want to put words in your mouth. But we add' = add(1, $, $) In my opinion, this is actually a bit more readable. The more complicated example now looks as follows: length = foldr((+), 0, $) . map(const(1), $) I actually find this form more clear. It kinds of shows the "flow" of the data that you feed into Additionally, such a feature allows partial applications of not just the first parameter, which does not work by -- this is all colors. ever made. This feature does have some limitations, for instance when we have multiple nested function calls, but in those So, despite its elegance, the curried function style doesn't really make partial application more powerful; with a When you first learn functional programming and finally understand curried function types, it's like you're looking Even more beautifully, it's essentially an inductive "shape", which reflects the dichotomy between imperative First of all, performance is a bit of a concern. When you call a curried function like add 2 3, the More importantly, curried function types have a weird shape. The whole idea about functions is that they take an A consequence of this weird shape is a kind of asymmetry: if a function returns multiple outputs, it returns a sayHi name age = "Hi I'm " ++ name ++ " and I'm " ++ show age The real issue here is that map is very generic and expects a function of the form I personally faced another consequence while doing a project involving predicates that said something about Definition P {In Out : Type} (f : In -> State Out) := If you define a function the "recommended" way, in a curried style, you get something like I realize that functional languages are not suddenly going to switch to the tuple style all of a sudden; millions Don't take the article too seriously or as an absolute judgment that the tupled style is always better. I admit I'd also love to hear if you know any (dis)advantages of curried functions other than the ones mentioned. I've I did want to mention one case where the curried style is superior, although it is not very common. If you live in (* an instance of [fin n] is a pair of a number and a proof that x < n *) Here, plus1 has type forall n : nat, fin n -> fin (n + 1), which is a curried dependent (* hypothetical syntax *) This would have a type like forall arg : { n : nat & fin n }, fin (arg.1). Not the worst thing in Dependent types are famously immensely difficult to work with and make type inference undecidable, so even when [1] The introduction chapter of the Cpdt book. Adam Chlipala. Certified Programming with Dependent Types. URL: Also on MIT Press. Copyright emilia-h 2025-2026 licensed under about site repo atom feed |
Currying, a fundamental concept in purely functional programming, involves defining functions by staggering their parameters—applying a function to the first argument results in a new function that takes the remaining arguments, and so on. This approach, exemplified by the `add` function defined as `add = \x -> (\y -> (\z -> x + y + z))` and its application `((add 1) 2) 3`, demonstrates a left-to-right evaluation order facilitated by arrow notation and left-associative function application. While elegant and commonly employed, this style raises certain considerations. Programming languages offer diverse approaches to multi-parameter function definitions. The imperative style, prevalent in languages like Rust, utilizes a straightforward parameter list—`fn f(p1: P1, p2: P2, p3: P3) -> R { ... }`. This contrasts with the curried style, standard in languages like Haskell, where `f p1 p2 p3 = ...` defines a function with three parameters. Additionally, the tuple style provides a pragmatic alternative, utilizing a single function with a tuple parameter carrying multiple values, as seen in Rust’s `fn f((p1, p2, p3): (P1, P2, P3)) -> R { ... }`. Although some imperative languages support these functional styles, they can introduce complexity. A key argument against the exclusive adoption of currying revolves around partial application. Partial application, the mechanism of fixing one or more parameters of a multi-argument function, producing a new function with fewer arguments, is intrinsically linked to the curried style. The example of the `add` function, where `add'` and `add''` are created by fixing the first and second parameters respectively, highlights this elegance. This is particularly prominent in higher-order functions like `map` and `foldr`, showcasing the power of partial application paired with functional composition. However, the notion that partial application is solely a characteristic of curried functions is misleading. Partial application is achievable with parameter lists and tuple styles, although requiring additional syntax, such as a "hole operator" in the described example. Despite its elegance, the curried style doesn't necessarily offer greater power. The inherent asymmetry of curried functions—where inputs are staggered but outputs coalesce into a tuple—can hinder composition, as demonstrated by the `sayHi` example, which fails to unify due to type mismatching between the defined function and the intended usage with a list of people. More fundamentally, the shape of curried function types—with inputs being sequentially defined but outputs being a final tuple—creates a somewhat awkward representation that can lead to inefficiencies. The tuple style provides a more aligned representation that avoids this unnecessary complexity. Performance is another relevant concern. The iterative nature of currying—creating intermediate functions for each applied parameter—can incur overhead, although optimizer improvements may mitigate this. Ultimately, the tuple style, with its simpler function definitions, arguably offers more clarity and efficiency, particularly in projects where function composition and partial application are heavily utilized. The subjective appeal of currying stems from a deeper perceptual experience—a sense of elegance and control derived from the “flow” of data through successively applied functions, reminiscent of a logical argument unfolding step-by-step. This sentiment, expressed by the author, isn't a technical justification but an aesthetic and cognitive preference, driving a fondness for the compact, elegant syntax of curried functions. This subjective appreciation, combined with the fundamental features of the style—namely, left-to-right evaluation, partial application, and a focus on inductive definitions—contributes to its popularity within functional programming communities. Finally, the discussion briefly touches upon dependent function types, a more advanced technique where function return types become dependent on the input values. This feature is prominent in languages like Coq, and simplifies expressing certain types of logical constraints. However, dependent type systems are notoriously complex and difficult to manage, highlighting a trade-off between expressive power and program maintainability. |