Skip to content
Tech News
← Back to articles

A case against currying

read original get Currying Function Puzzle Game → more articles
Why This Matters

This article challenges the widespread use of currying in functional programming, highlighting potential drawbacks such as loss of clarity and flexibility compared to imperative and tuple-based styles. Understanding these differences is crucial for developers choosing the most effective approach for their applications, especially as functional paradigms influence modern software development. Recognizing when to use or avoid currying can lead to more maintainable and efficient codebases.

Key Takeaways

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 add = \x -> (\y -> (\z -> x + y + z)) add :: Int -> (Int -> (Int -> Int)) ((add 1 ) 2 ) 3

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 { ... } f(a1, a2, a3) f : fn ( P1 , P2 , P3 ) -> R

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

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

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) = ... f(a1, a2, a3) f :: ( P1 , P2 , P3 ) -> R

... continue reading