Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Currying: From Logic to Functional Programming

Hello! Welcome back.

In our last lesson on function composition, we saw how Haskell could elegantly create a 2D mapping function with map . map. I mentioned that this was possible because of a concept called currying, which allows functions to be applied to their arguments one at a time. Today, we'll explore that concept in detail.

This lesson focuses on currying, a fundamental technique in functional programming. We'll start with its formal definition and historical roots in logic and mathematics. Then, we'll see how it's a core design principle in languages like Haskell and the ML family. Finally, we'll clarify the relationship between currying and the closely related idea of partial application.

Your goal for this lesson is to be able to apply currying to transform multi-argument functions, referencing its origins in logic and its central role in ML and Haskell.

1. The Formal Concept and Origins of Currying

At its core, currying is the process of transforming a function that takes multiple arguments into a sequence of functions, each taking a single argument.

Instead of a function f that takes two arguments, say x and y, and computes a result f(x, y), we create a new function g. This new function g takes only the first argument x and returns another function. This returned function then takes the second argument y and produces the final result. So, the call becomes g(x)(y).

This diagram contrasts a standard multi-argument function call `f(a, b, c)` with a curried call. In the curried version, the function takes `a` and returns a new function, which takes `b` and returns another new function, which finally takes `c` to produce the result.

This technique isn't just a programming trick; it has deep roots in mathematical logic. To understand its formal definition and history, please read the following sections from the Wikipedia article on the topic.

Currying

This article provides an excellent overview of currying, including its mathematical foundation and historical context, which directly addresses key parts of our learning outcome.

Please read the following sections: The introductory section (before the 'Motivation' heading) to get the main idea. The 'History' section to learn about the logicians Haskell Curry, Moses Schönfinkel, and Gottlob Frege. The 'Definition' section for the formal mathematical transformation. The 'Logic' section to see the connection to the Curry-Howard correspondence.

As you read, take note of these key points:

  • The Transformation: A function of type (X × Y) → Z is transformed into a function of type X → (Y → Z). The first takes a pair of arguments; the second takes one argument and returns a function.
  • The History: The concept is named after Haskell Curry, but the idea was developed earlier by Moses Schönfokenkel and can be traced back to Gottlob Frege.
  • The Logical Equivalence: Under the Curry-Howard correspondence, this transformation is equivalent to the logical rule of exportation: ((A ∧ B) → C) ⇔ (A → (B → C)). This connects the structure of programs to the structure of proofs, a central idea in theoretical computer science.

2. Currying in Practice: The Haskell Approach

While you can manually curry functions in many languages (as we'll see later), some languages are designed with currying as a default, fundamental principle. Haskell and languages in the ML family (like OCaml and F#) are the most prominent examples.

In Haskell, all functions are considered to take only one argument. What looks like a multi-argument function is actually syntactic sugar for a curried function.

Currying - HaskellWiki

The HaskellWiki page on currying provides a concise and direct explanation of how currying is integrated into the language's design.

Read the first few paragraphs, down to the end of the div example. Focus on how the type signature a -> b -> c is interpreted as a -> (b -> c) and how function application f x y is interpreted as (f x) y.

This "curried by default" nature is a cornerstone of Haskell's design. To see this mechanism in action, let's watch a video that breaks it down step-by-step.

Currying

This video from the 'Functional Programming in Haskell' series provides a clear, visual explanation of how curried functions are evaluated and how their types are constructed.

Please watch the sections from 02:01 to 09:16. Pay attention to: How the plus function consumes one argument and transforms into a new function (02:01 - 03:31). The process of working backward from the result to determine the function's type signature (03:31 - 07:15). How Haskell's syntax uses implicit bracketing for types (right-associative) and function application (left-associative) to make currying feel natural (07:15 - 09:16).

The video illustrates a key mental model for functional programming: functions are not just procedures but also values that can be created and returned. When you give a curried function its first argument, you are not just providing data; you are creating a new, more specialized function.

Given your familiarity with lambda calculus from your multi-agent AI studies, you can think of this transformation as f(x,y) becoming λx.(λy.f(x,y)). Haskell simply makes this the default representation.

3. Currying vs. Partial Application

The primary benefit of currying is that it makes partial application seamless and elegant. Partial application is the act of fixing a number of arguments to a function, producing another function of smaller arity.

The two concepts are often confused, but the distinction is important:

  • Currying is a transformation that converts a function taking multiple arguments into a chain of functions each taking a single argument.
  • Partial Application is the application of a function to fewer than its total number of arguments.

In a curried language like Haskell, partial application is a natural consequence of the evaluation model. If you provide add (which takes two arguments) with only one argument, add 5, you don't get an error. You get back the partially applied function that adds 5 to anything.

This next video explains this relationship very clearly.

Haskell for Imperative Programmers #7 - Partial Function Application & Currying

Philipp Hagenlocher's video connects the theory of currying directly to the practical utility of partial application.

Watch the video from the beginning to 03:28. Focus on: The introduction, which frames partial application as a result of currying (00:00 - 01:00). The explanation of what happens when you call add 1 in Haskell (01:15 - 02:49). The practical example of creating a doubleList function by partially applying map (02:34 - 03:28).

The doubleList = map (*2) example is a perfect illustration of why this matters. We created a new, specialized function without writing a new def or lambda. This is the mechanism that powered the map . map composition from our previous lesson. The first map in the composition is partially applied, creating a new function that is then ready to be composed with the second map.

For a more formal distinction, let's return to the Wikipedia article.

Currying

This section of the Wikipedia article uses formal notation to make the distinction between currying and partial application unambiguous.

Please read the section 'Contrast with partial function application'. Note how it contrasts the type signatures and evaluation of a curried function versus a partially applied one.

Conclusion

In this lesson, we've unpacked the concept of currying and its role in functional programming.

Key Takeaways:

  • Currying as a Transformation: Currying transforms a function that accepts multiple arguments, f(x, y, ...z), into a chain of functions that each accept a single argument: g(x)(y)...(z).
  • Logical and Historical Roots: The concept originates with logicians like Frege and Schönfinkel and was popularized by Haskell Curry. It has a direct parallel in logic via the Curry-Howard correspondence.
  • Central Role in Haskell/ML: In these languages, all functions are curried by default. A function type a -> b -> c is syntactic sugar for a -> (b -> c), meaning a function that takes an a and returns a function of type b -> c.
  • Enabling Partial Application: Currying makes partial application a natural and syntactically lightweight operation. Applying a function to fewer than its total arguments simply returns a new, specialized function.

Preview of the Next Lesson:

We've seen how partial application flows naturally from currying in Haskell. In our next lesson, we'll focus explicitly on partial application as a technique in its own right. We will compare the implicit approach in Haskell with the more explicit methods required in Python, using its functools.partial utility, to understand the design trade-offs involved.

Can't find a good explanation? Sign up and we'll make it for you

Sign up