jonnifyfsharp

Dive C1.03.3

Partial application

Supply some arguments now; a new, specialised function comes back for the rest.

Currying made every F# function a chain of one-argument steps. Partial application stops part-way: supply some arguments and keep the function that the chain returns. From the curried add, the call add 10 is a complete value — a function of type (int -> int) that adds ten. This is the move that makes List.map (add 10) read cleanly, and it pairs with the pipe and F#'s data-last convention.

C1.03.3 · part 1

Some arguments now, a function back

Apply fewer arguments than a function has parameters, and the result is a function expecting the rest. From add, the call add 10 binds the first argument and leaves the second open — a new function that adds ten.

let add x y = x + y
let add10 = add 10            // val add10 : (int -> int)
add10 5                      // 15

let multiply x y = x * y
let double = multiply 2       // val double : (int -> int)
List.map double [1; 2; 3]    // [2; 4; 6]

Each specialised function is an ordinary value with the type the F# Interactive prints in parentheses, (int -> int). double is multiply 2: give it any int and it doubles it. Passed to List.map, it runs across the whole list.

partial application
supply some of a function's arguments; the rest become a new function.
add 10
type (int -> int) — a function that adds ten.
multiply 2
type (int -> int) — a function that doubles.
List.map (add 10)
maps the specialised function across a list.
C1.03.3 · part 2

Data-last and the pipe

FSharp.Core puts the collection last in its functions: List.map takes the function first and the list second. That ordering exists so partial application leaves exactly the right hole. List.map (add 10) is a partially applied List.map — a function still waiting for its list — and the pipe |> supplies it.

let add x y = x + y

[1; 2; 3] |> List.map (add 10)   // [11; 12; 13]

// the same, named in two steps
let addTen = List.map (add 10)   // val addTen : (int list -> int list)
addTen [1; 2; 3]                  // [11; 12; 13]

The expression x |> f means f x: it feeds x in as the last argument. Because the list is the last argument of List.map, the pipe and partial application fit together — the function is built with add 10, and the data arrives through the pipe.

Data-last is a convention, not a language rule, but FSharp.Core follows it across List, Array, and Seq. It is why partially applied collection functions slot onto the end of a pipe.

A partial-application builder

Pick a base function and a first argument. The builder forms the specialised function, then maps it across the fixed list [1; 2; 3; 4; 5]. The readout shows the F# line and the list it evaluates to.

base fn + first arg → specialised fn → List.map over [1; 2; 3; 4; 5]

base function

first argument

general fn + arg add 10 specialised fn fun y -> 10 + y (int -> int) List.map over [1..5] new list [11; 12; …] fix one argument, keep the function the rest returns
F# [1; 2; 3; 4; 5] |> List.map (add 10) = [11; 12; 13; 14; 15]

A general function plus a fixed argument is a specialised one. Partial application names the hole the data fills, and the pipe fills it.

Partial application

Fix some of a curried function's arguments; the rest become a new, named-or-passed function.

F# today

[1; 2; 3] |> List.map (add 10) builds the function with add 10 and pipes the list in.

The three together

Three facts, one shape. A function is a value, so it can be passed and returned. Currying makes every function a chain of one-argument steps. Partial application stops the chain part-way and keeps the function that comes back. Together they explain why F# code reads as pipelines of small, specialised functions rather than loops over arguments.

References

Sources

Related in this course