jonnifyfsharp

Dive C1.03.2

Currying

Every function takes one argument; the type int -> int -> int tells you exactly how.

A function with two parameters is, in F#, a function of one argument that returns a function of one argument. This is currying, named for Haskell Curry. It is why the inferred type of add x y = x + y is int -> int -> int and not a pair-taking signature: the arrow associates to the right, so the type reads int -> (int -> int). Application, by contrast, associates to the left, so add 3 4 means (add 3) 4.

C1.03.2 · part 1

One argument, then another

The function add is written with two parameters, but it is built from nested one-argument functions. The definition below and the equivalent lambda form are the same value; add takes x and returns the function fun y -> x + y.

let add x y = x + y           // val add : int -> int -> int

// the same value, written out as nested one-argument functions
let add = fun x -> (fun y -> x + y)

Supply one argument and a function comes back. Binding the result of add 3 to a name shows its type: a function from int to int. The F# Interactive prints the signature of a partially applied value with parentheses around the function type.

let add3 = add 3              // val add3 : (int -> int)
add3 4                       // 7
add 3 4                        // 7   ==  (add 3) 4
currying
a function of many parameters is nested one-argument functions, each returning the next.
arrow (->)
associates to the right: int -> int -> int is int -> (int -> int).
application
associates to the left: add 3 4 is (add 3) 4.
(int -> int)
the F# Interactive prints a partially applied function's type in parentheses.
C1.03.2 · part 2

Reading the type

The signature int -> int -> int has two arrows. Because the arrow associates to the right, the second arrow binds first: the type is one int mapping to the function (int -> int). The diagram makes the right-association bracket explicit.

int -> int -> int · the arrow associates right

int x -> int y -> int result int -> int : groups first int -> (int -> int) one int in, a function out
type int -> int -> int = int -> (int -> int) · add 3 has type (int -> int)

Two arrows, one reading: add takes an int and hands back a function. The signature was never a two-argument type; it was always one argument at a time.

Unfolding add 3 4

Step through the call. Left-associated application means add 3 4 is (add 3) 4: apply add to 3 first, producing a function, then apply that to 4. Each step highlights the part about to reduce.

add 3 4 · one argument at a time

  1. add 3 4 application is left-associated: (add 3) 4
  2. (add 3) 4 apply add to 3 · add 3 : (int -> int)
  3. (fun y -> 3 + y) 4 the function returned by add 3
  4. 3 + 4 apply it to 4 · substitute y := 4
  5. 7 the value
step 1 of 5 · add 3 4 groups as (add 3) 4 · next, apply add to 3

A two-argument call is two one-argument calls in a row. The first produces a function; the second runs it. Currying is what lets the first stand on its own — the next dive keeps it.

Currying

A function of many parameters is one-argument functions nested, each returning the next.

F# today

add 3 is a value of type (int -> int) — a usable function on its own.

Where this leads

Currying is the mechanism; partial application is the use. Because add 3 is already a complete value, a program can supply some arguments now and the rest later — naming the intermediate function, or passing it straight to List.map. Dive C1.03.3 turns the unfolding into a tool.

References

Sources

Related in this course