Currying and Partial Application
Prerequisites: Pipelines and composition
Every F# function really takes one argument. A function "with two
parameters" is a function that takes the first argument and returns a
function waiting for the second. This is called currying, and it's
why the signature is written int -> int -> int — which means
int -> (int -> int).
Partial application
Call a function with fewer arguments than it expects, and you get back a function waiting for the rest:
let add a b = a + b
let add10 = add 10 // int -> int
printfn "%d" (add10 5)
printfn "%d" (add10 32)
let multiply a b = a * b
let triple = multiply 3
printfn "%A" (List.map triple [1; 2; 3])
15
42
[3; 6; 9]
Why it matters: pipelines
List.map triple is itself a partial application: List.map is waiting
for its list. That's exactly what |> supplies:
let applyDiscount percent price = price - price * percent / 100.0
let addTax rate price = price * (1.0 + rate)
let finalPrice =
100.0
|> applyDiscount 20.0
|> addTax 0.1
printfn "%.2f" finalPrice
88.00
applyDiscount 20.0 is a function float -> float, and so is
addTax 0.1 — which is why they chain so neatly. This is the payoff of
putting the data parameter last.
Partial application with printf
let logInfo = printfn "[INFO] %s"
logInfo "Server started"
logInfo "Listening on port 8080"
[INFO] Server started
[INFO] Listening on port 8080
Tupled parameters
If you write parameters in parentheses with commas, the function takes
one tuple and can't be partially applied. .NET methods (like
System.Math.Max(3, 7)) are called this way:
let addTupled (a, b) = a + b
printfn "%d" (addTupled (2, 3))
printfn "%d" (System.Math.Max(3, 7))
5
7
Prefer curried parameters for your own F# functions; tupled ones are mainly for .NET interop and methods on classes.
let f a b = ...is curried:f 1is a valid function.- Order parameters from "most fixed" to "most variable" (data last).
- Too few arguments by accident? The compiler will tell you that you have a function where it expected a value.