On this page

Currying and Partial Application in Real Use

Published on July 21, 2026

Enrico Hochkoeppler Profile

By Enrico Hochkoeppler

Web Developer & TutoriaLibre Founder

Currying and partial application are related but distinct ideas, and the distinction matters once they show up in real code rather than toy examples. Currying is the underlying fact that every OCaml function of several arguments is really a chain of one-argument functions. Partial application is what that fact makes possible in practice: supplying only some of a function's arguments up front to produce a new, more specific function, ready to be finished off with the remaining arguments later.

From Curried Definition to Specialized Function​

The clearest everyday use of partial application is turning a general-purpose function into a specialized one, simply by fixing some of its arguments ahead of time:


let add x y = x + y

let add_five = add 5

# add_five 10;;

- : int = 15

add_five is a genuine function value, of type int -> int, produced by supplying only the first of add's two eventual arguments. Nothing special had to be written to make add_five possible — it falls directly out of add being a curried, one-argument-at-a-time function to begin with. This same pattern applies just as naturally to built-in operators, since they're ordinary curried functions too:


# let double = ( * ) 2;;

val double : int -> int = <fun>

# double 21;;

- : int = 42

Partial Application Inside Higher-Order Functions​

Partial application shows up constantly as an argument to List.map, List.filter, and similar functions, precisely because it avoids writing out an anonymous function just to plug in one fixed value:


List.map (add 5) [1; 2; 3]       (* [6; 7; 8] *)

List.filter (( < ) 3) [1; 2; 3; 4; 5]  (* [4; 5], keeps x where 3 < x *)

add 5 and ( < ) 3 are both already-complete function values by the time they're passed in — there's no need to wrap either one in fun x -> ... first, since partial application already produced exactly the one-argument function each higher-order function expects.

Fitting Partial Application Into a Pipeline​

Partial application becomes especially useful once a value is being threaded through a pipeline with |>, since the function on the right-hand side of |> only ever receives one argument: whatever was piped in. Any function that naturally takes its "main" argument somewhere other than last needs some adjustment to fit. List.nth, for instance, takes the list first and the index second — the reverse of what a pipeline handing over a list would need for extracting a specific position. Fun.flip, which swaps the order of a two-argument function's parameters, combines with partial application to solve exactly this:


let second_word sentence =

  sentence

  |> String.split_on_char ' '

  |> Fun.flip List.nth 1

Fun.flip List.nth 1 partially applies the flipped version of List.nth to the index 1, producing a one-argument function — "get element 1 of" — that's exactly what the pipeline needs at that point, ready to receive the list piped in from the previous step.

Tuple-Style Functions Can't Be Partially Applied​

Not every multi-argument function in OCaml is written in curried style. A function can also take its arguments bundled into a single tuple:


let add_tupled (x, y) = x + y

# add_tupled;;

- : int * int -> int = <fun>

add_tupled takes exactly one argument — a pair — rather than two arguments one at a time, and there's no way to "partially apply" a tuple, since the whole pair has to exist before the function can be called at all. This is the practical trade-off between the two styles: curried functions support partial application directly; tuple-style functions don't, but can sometimes avoid allocating an intermediate closure when a function is genuinely always called with every argument at once. In everyday OCaml code, the curried style is the default and by far the more common choice, specifically because partial application is valuable often enough to be worth keeping available.

A Performance Note​

Calling a curried function with every one of its arguments at once carries no meaningful runtime penalty compared to a tupled equivalent — the compiler generates efficient code for the fully-applied case either way. Partial application does carry a small, real cost, since it involves allocating an intermediate function value (a closure) to hold onto the arguments supplied so far. In ordinary application code, this cost is negligible and essentially never worth avoiding for its own sake; it's worth being aware of mainly in code that's already been identified as performance-critical through profiling, rather than something to preemptively avoid everywhere else.

A Gotcha Worth Knowing: The Ignored Partial Application Warning​

A curried function applied to fewer arguments than it needs doesn't fail — it just produces a function value, which is exactly the mechanism partial application relies on. That behavior becomes a real trap when it happens by accident. Consider a sequence of two expressions separated by ;, which requires the first one to have type unit:


let () =

  print_int;

  print_newline ()

print_int has type int -> unit, not unit — the argument was simply forgotten. Because a curried function missing an argument is a perfectly valid expression on its own, this doesn't fail to compile the way a genuine type error would; instead, the compiler emits a warning about an ignored partial application, flagging that a function is sitting somewhere a plain value was expected and pointing out that some arguments are probably missing. Treating this warning as worth fixing immediately, the same way a missing pattern-match case is worth fixing immediately, is important: it's very often the compiler catching a real, otherwise-silent bug — a function call that looks complete but never actually runs.

Key Takeaways​

Found this helpful? Share the knowledge...

... or support this project to keep it alive!

Buy Me a Coffee

Previous

Function Composition & Pipe Operator

Next

Mutable State