LmCast :: Stay tuned in

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
a functional language. In purely functional languages, the convention is to define an n-parameter function
inductively by staggering the parameters: applying the function to argument #1 returns a function that takes
parameters 2..n, which in turn can be applied to argument #2 to return a function that takes parameters 3..n, etc.
until all the arguments are given and the result is returned. For instance, we can define a 3-parameter
add function that adds three numbers:

add x y z = x + y + z
-- this is syntactic sugar for:
add = \x -> (\y -> (\z -> x + y + z))
-- add has the following type:
add :: Int -> (Int -> (Int -> Int))

-- you can apply left-to-right:
((add 1) 2) 3 -- returns 6

We make the arrow -> right-associative, so we can write Int -> Int -> Int -> Int.
Additionally, we make function application left-associative, so we can write add 1 2 3 and minimize
our use of parentheses.

I want to argue that when you define functions in this style, although it is nice and elegant, there are also some
things that are lost.

There are roughly three different "styles" that programming languages offer for writing multi-parameter function
definitions with. Firstly, there is the imperative style which I will call the "parameter list" style. Here,
multiple parameters is a baked-in feature of functions. This is the default in imperative languages like Rust:

fn f(p1: P1, p2: P2, p3: P3) -> R { ... } // definition
f(a1, a2, a3) // call
f : fn(P1, P2, P3) -> R // type

Another form is the "curried" style, offered in pure functional languages like Haskell:

f p1 p2 p3 = ... -- definition
f a1 a2 a3 -- call
f :: P1 -> P2 -> P3 -> R -- type

Finally, there is the "tuple" style. It looks similar to the parameter list style, but the multiple parameters are
not part of the functions itself. The function just has one parameter, but that parameter is a tuple so it
effectively carries multiple values. This is usually possible in functional languages like Haskell as well but not
the standard:

f(p1, p2, p3) = ... -- definition
f(a1, a2, a3) -- call
f :: (P1, P2, P3) -> R -- type

Note that some imperative languages also offer these functional styles, but it gets a bit unwieldy. For instance,
we can do the curried style in JavaScript:

const f = p1 => p2 => p3 => ...;
f(a1)(a2)(a3)

We can also do the tuple style in Rust:

fn f((p1, p2, p3): (P1, P2, P3)) -> R { ... }
f((a1, a2, a3))
f : fn((P1, P2, P3)) -> R

Ultimately, even though these styles have different practical use cases, they are equivalent in theory: the types
(P1, P2) -> R and P1 -> P2 -> R are
isomorphic, which means there is a one-to-one mapping
between functions of those types. So, what's the reason for preferring the currying style to the others?

Partial Application

When you ask the internet why we do curried functions, the main response is
"because it makes partial application straightforward". Partial
application is a mechanism where you fix one of the parameters of a multi-argument function to a certain value,
and get a new function that only takes the rest of the parameters as input. It is true that partial application is
very natural and elegant for the currying style; for instance, if we take our 3-parameter add from
before, you can do the following in Haskell:

add' = add 1
-- now, add' = \y -> (\z -> 1 + y + z)
add' :: Int -> Int -> Int

add'' = add' 2
-- now, add'' = \z -> 1 + 2 + z
add'' :: Int -> Int

add'' 3 -- returns 6

This is especially nice when we have higher-level functions like map and fold. For
instance, we can do pretty crazy cool things with partial applications and function composition:

length = foldr (+) 0 . map (const 1)
length2d = foldr (+) 0 . map length
length2d [[1, 4, 2], [], [7, 13]] -- returns 5

However, it is often wrongly assumed that the option of doing partial application is a special property of curried
functions! We can totally do partial application for functions in the parameter list style or tuple style. If we
redefine add in the tuple style, we get something like the following:

add(x, y, z) = x + y + z
add :: (Int, Int, Int) -> Int

add' = let x = 1 in \(y, z) -> add(x, y, z)
add'' = let y = 2 in \z -> add'(y, z)
add''(3) -- returns 6

"But," I hear you say, "this clearly looks horrible." Or maybe not, I don't want to put words in your mouth. But we
can easily define a bit of syntactic sugar to make it look nice, for instance by defining a $
"hole operator":

add' = add(1, $, $)
add'' = add'(2, $)
add''(3) -- returns 6

In my opinion, this is actually a bit more readable. The more complicated example now looks as follows:

length = foldr((+), 0, $) . map(const(1), $)
length2d = foldr((+), 0, $) . map(length, $)
length2d([[1, 4, 2], [], [7, 13]]) -- returns 5

I actually find this form more clear. It kinds of shows the "flow" of the data that you feed into
length or length2d: first into the second parameter of map, then the result
of that is fed into the third parameter of foldr.

Additionally, such a feature allows partial applications of not just the first parameter, which does not work by
default for the curried style. For instance if we want to fix the second parameter of map:

-- this is all colors. ever made.
allColors = ["red", "green", "blue"]
forEachColor = map($, allColors)

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.

So, despite its elegance, the curried function style doesn't really make partial application more powerful; with a
little bit of syntactic sugar we can easily emulate its powers. However, I suspect there is another, more
"vibe-based" reason why functional programmers have a dependence on curried functions stronger than water or sleep.

"It's cool"

When you first learn functional programming and finally understand curried function types, it's like you're looking
inside the matrix. It's so cool that when you make -> right-associative and function application
left-associative, successively applying a curried function to multiple parameters unfolds beautifully! We don't
even have to write parentheses when doing function application. And we get partial application completely for free,
so we can write convoluted definitions for calculating the length of a 2D list and feel very clever.

Even more beautifully, it's essentially an inductive "shape", which reflects the dichotomy between imperative
languages and functional languages: they've got their parameter lists, which are more like arrays (iterative), but
we have curried functions, which are more like lists (inductive). It makes me feel all warm and fuzzy inside just
thinking about the elegance! This is clearly meant to be!

Why Not Curried Functions?
Just because you can, doesn't mean you should. There are some good reasons to prefer the tuple style.

First of all, performance is a bit of a concern. When you call a curried function like add 2 3, the
add 2 first evaluates to a new function expression \y -> add 2 y, which is then applied
to 3. Every call to a multi-parameter function creates a bunch of intermediate functions. However, I'm
sure a good enough optimizer can eliminate that overhead, so this is not really the greatest concern.

More importantly, curried function types have a weird shape. The whole idea about functions is that they take an
input and give back an output, so they have types like In -> Out. When you unify this with a curried
function type like P1 -> P2 -> P3 -> R, you get In = P1 and
Out = P2 -> P3 -> R. When you unify it with a tupled function type like
(P1, P2, P3) -> R, you get In = (P1, P2, P3) and Out = R, which seems more
logical.

A consequence of this weird shape is a kind of asymmetry: if a function returns multiple outputs, it returns a
tuple, but if it takes multiple inputs, you instead stagger them. As a result, curried functions often don't
compose nicely.

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)

The real issue here is that map is very generic and expects a function of the form
In -> Out but curried functions don't have this shape. To make this work we have to instead pass
uncurry sayHi as our mapping function. In this 2-parameter example that isn't so bad, but it gets
worse with more parameters.

I personally faced another consequence while doing a project involving predicates that said something about
monad-returning functions in the Rocq proof assistant. The predicate looked something like the following
(parameters in {} are implicit type parameters):

Definition P {In Out : Type} (f : In -> State Out) :=
...some proposition about f...

If you define a function the "recommended" way, in a curried style, you get something like
f : P1 -> ... -> Pn -> State R. This does not unify with In -> State Out at all, so
P(f) is not even well-typed! I had to manually uncurry the function every time. :(

Conclusion

I realize that functional languages are not suddenly going to switch to the tuple style all of a sudden; millions
of lines of Haskell have been written and curried functions are just how it's done right now. Nobody is going to
change all of that because someone on the internet thought it would be a good idea :). However, if you ever happen
to be writing a functional language or standard library, consider experimenting with the tuple style and an
alternative syntax for partial application.

Don't take the article too seriously or as an absolute judgment that the tupled style is always better. I admit
that for some higher-order functions, like map and fix, a curried definition just seems
too nice to resist. I just think for most purposes the tuple style makes a lot more sense.

I'd also love to hear if you know any (dis)advantages of curried functions other than the ones mentioned. I've
written a fair bit of functional code, but I'm by no means an expert on the topic and would always love to learn
more. And it's possible I just wrote this article because I was salty that I kept having to uncurry functions
during my proof assistant projects.

Sidenote: Dependent Function Types

I did want to mention one case where the curried style is superior, although it is not very common. If you live in
a dependently typed language like Gallina (the language
of the Rocq/Coq proof assistant) or Agda, you can have the return type of a function depend on one of the inputs
(not one of the input types, one of the input values), or you can have the type of the second parameter of
a function depend on the value of the first. For instance, a common example of a dependent type is a natural number
that is guaranteed to be upper-bounded by another natural number n:

(* an instance of [fin n] is a pair of a number and a proof that x < n *)
Definition fin (n : nat) := { x : nat & x < n }.
Definition plus1 (n : nat) (i : fin n) : fin (n + 1) :=
(i.1 + 1 ; (* proof that i.1 + 1 < n + 1 *)).

Here, plus1 has type forall n : nat, fin n -> fin (n + 1), which is a curried dependent
function type with two parameters: n and i. The forall specifies that other
parameters and the return type may depend on the value of the first parameter n. Say you had a version
of this in the tupled style. Then the parameters would have to be a dependent tuple, where the type of the
second tuple element may depend on the value of the first tuple element:

(* hypothetical syntax *)
Definition plus1 (n : nat ; i : fin n) : fin (n + 1) :=
(i.1 + 1 ; (* proof that i.1 + 1 < n + 1 *)).

This would have a type like forall arg : { n : nat & fin n }, fin (arg.1). Not the worst thing in
the world, but I would say definitely more unwieldy.

Dependent types are famously immensely difficult to work with and make type inference undecidable, so even when
you're working in a language with this capability, it's best to avoid dependent types in function parameters.
Having said that, some people swear by them [1] and
manage to write large codebases that make extensive use of them.

References

[1] The introduction chapter of the Cpdt book.
Citation for the book:

Adam Chlipala. Certified Programming with Dependent Types. URL:
http://adam.chlipala.net/cpdt/html/toc.html

Also on MIT Press.

Copyright emilia-h 2025-2026 licensed under
CC BY-SA 4.0

about
codeberg

site repo
github

atom feed
emilia144@proton.me

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.