Currying

Multi-argument functions as chains of single-argument functions.

10 topics • ~935 words

A composing stick can have more than one tagged blank. A stick tagged x and y has two positions waiting for type pieces. But the compositor fills only one tag per round of setting type. After pressing a piece into every x blank, the result is a new stick -- still waiting for y. One tag at a time, one round at a time.

Lambda calculus has no built-in way to pass two arguments at once. Every abstraction binds exactly one parameter. But this is not a limitation -- it is a design. By nesting abstractions, you get multi-argument functions that are more flexible than any multi-parameter syntax could be. This idea is called currying.

What Is Currying?

In lambda calculus, every function takes exactly one argument. To handle multiple arguments, we nest abstractions:

λx.λy.M

This is a function that takes an argument for x, and returns a new function that takes an argument for y. The arguments are consumed one at a time, not as a pair.

This technique is called currying (after logician Haskell Curry).

Two-Argument Function

When you see λx.λy.M, read it as: "a function that takes x, then takes y, and produces M."

The outer λx binds the first argument. The inner λy binds the second. The body M can use both x and y.

Examples:

  • λx.λy.x -- takes two arguments, returns the first
  • λx.λy.y -- takes two arguments, returns the second
  • λx.λy.x y -- takes two arguments, applies the first to the second

First Argument

A stick has blanks tagged x and y. The compositor sets type for x with the piece a. Every x blank is filled. The y blanks remain -- the result is a new stick, still waiting for one more piece.

When a curried function receives its first argument, beta reduction fills the outer parameter and leaves the inner abstraction intact.

(λx.λy.x) a →β λy.a

The x in the body is replaced by a. The inner λy remains -- the result is a new function that still expects one more argument.

Second Argument

After the first argument has been applied, the result is a simpler function. Applying the second argument completes the computation.

(λy.a) b →β a

The body is a, which contains no y. The substitution a[y := b] leaves a unchanged -- y does not appear free in a, so there is nothing to replace. The second argument b is discarded.

Full Application

Two rounds of setting type. First round: press a into every x blank. The stick now reads λy.a. Second round: press b into every y blank. But there are no y blanks left -- the stick already reads a. Done.

Applying a curried function to all its arguments requires multiple beta reductions, one per argument. Application is left-associative, so (λx.λy.x) a b means ((λx.λy.x) a) b.

  • Step 1: ((λx.λy.x) a) b →β (λy.a) b
  • Step 2: (λy.a) b →β a

The final result is a -- the first of the two arguments.

Why Nested Lambdas?

Lambda calculus could have been designed with multi-parameter abstractions like λ(x, y).M. But it was not. Instead, every abstraction binds exactly one parameter, and multiple arguments are handled by nesting: λx.λy.M.

This is not a limitation. Nesting gives us everything multi-parameter syntax would, without adding any new grammar rules. The calculus stays minimal: three term forms, one computation rule.

The tradeoff is simple: one extra reduction step per additional argument, in exchange for a simpler formal system.

Three Arguments

The nesting pattern extends naturally. Three parameters means three nested abstractions:

λx.λy.λz.M

This takes x first, then y, then z. Each application peels off one layer. After all three arguments are supplied, the body M (with all substitutions applied) is the result.

There is no limit to how deep the nesting can go. Four arguments, five, a hundred -- each is just one more layer of λ.

Currying Definition

Currying is the technique of expressing a function that takes multiple arguments as a chain of functions that each take one argument.

A function f(x, y) = M becomes λx.λy.M -- a function that takes x and returns a function that takes y.

The term honors logician Haskell Curry, though the idea was used earlier by Moses Schönfinkel and Gottlob Frege.

In lambda calculus, currying is not an optional technique -- it is the only way to handle multiple arguments, because every abstraction binds exactly one parameter.

One at a Time

Currying does more than simulate multi-argument functions. Because each argument is consumed separately, you can supply some arguments and stop. The result is a new, specialized function.

(λx.λy.x y) f →β λy.f y

You gave one argument (f) and got back a function that applies f to whatever it receives next. This intermediate result is useful on its own -- you can pass it around, apply it later, or give it to another function.

A multi-parameter system like λ(x, y).M would require all arguments at once. Currying gives you this flexibility for free.

Reading Curried Functions

Reading a curried function: count the nested λs to see how many arguments it expects, and read the body to see what it does with them.

λf.λx.λy.f x y

Three λs, so three arguments: f, x, y. The body f x y applies f to x, then applies the result to y.

Read it as: "takes a function f, a value x, and a value y, then applies f to x and y."

The outermost λ binds the first argument. The innermost λ binds the last argument. Arguments are consumed from outside in.

Ready to test your understanding?

Bitwit uses spaced repetition to help you truly master concepts like this—not just read about them. Each card generates with different values, so you can't just memorize answers.

Practice Currying →