- Courses
- 💻Programming Languages
- 🐫OCaml (WIP)
- Type Inference & Higher-Order Functions
- Function Composition & Pipe Operator
On this page
Function Composition and the Pipe Operator |>
Published on July 21, 2026
By Enrico Hochkoeppler
Web Developer & TutoriaLibre Founder
|> is one of the most visibly idiomatic pieces of OCaml syntax: it takes a value on its left and a function on its right, and applies that function to that value. On its own, that's a small thing. What makes it matter is what it does to the shape of code that processes a value through several steps in sequence — turning a deeply nested, inside-out expression into a flat, top-to-bottom pipeline that reads in the same order it actually executes.
What |> Actually Does
|> is defined, remarkably simply, as:
let (|>) x f = f x
Given a value x and a function f, x |> f is exactly f x — nothing more. The entire benefit comes from what happens when several of these are chained together, since |> is left-associative:
let result = 5 |> succ |> ( * ) 2 |> string_of_int
This reads left to right: take 5, apply succ to get 6, apply ( * ) 2 to get 12, apply string_of_int to get "12". Compare this to the equivalent written with ordinary nested parentheses:
let result = string_of_int (( * ) 2 (succ 5))
Both expressions compute the same thing, but the second one has to be read from the inside out — succ 5 happens first, even though it's written in the middle, and string_of_int happens last, even though it's written first. The piped version reads in execution order, which is exactly why it tends to be preferred once more than one or two steps are chained together.
@@: Application Without Parentheses, in the Original Order
A related operator, @@, also avoids nested parentheses, but keeps the function-before-argument order intact rather than reversing it:
let (@@) f x = f x
string_of_int @@ ( * ) 2 @@ succ 5
@@ is right-associative, so this expression is grouped as string_of_int @@ (( * ) 2 @@ (succ 5)) — evaluation still proceeds from the innermost application outward (succ 5 first), but without needing an actual pair of parentheses around it. The trade-off compared to |>: @@ avoids parentheses but still reads in the "last operation appears first" order that nested function calls naturally have; |> avoids parentheses and reorders the expression to read in the order it actually runs. This is why |> tends to be the more common choice specifically for describing a sequence of transformations, while @@ shows up more often for a single, one-off case of avoiding a couple of parentheses.
Piping Isn't the Same as Composing
It's worth being precise about a distinction that's easy to blur: piping a value through a chain of functions applies those functions immediately, one after another, to that specific value. Composing two functions, by contrast, produces a brand-new function — nothing gets applied to anything yet, and the result can be reused on many different values later.
OCaml's standard library provides |> and @@ directly, but doesn't provide a dedicated infix composition operator. Defining one is a common, small piece of idiomatic code:
let ( % ) f g x = f (g x) (* right-to-left, matching mathematical f(g(x)) *)
let ( %> ) f g x = g (f x) (* left-to-right, matching the reading order of |> *)
The difference between piping and composing becomes concrete side by side:
let squared_then_incremented = succ % (fun x -> x * x)
(* squared_then_incremented is a genuine, reusable function value *)
let result = 5 |> (fun x -> x * x) |> succ
(* result is a single, already-computed int *)
squared_then_incremented can be applied to as many different numbers as needed later, exactly like any other function; result is simply 26, a value that already exists, with nothing left to reuse. Reaching for |> is usually right when a value needs to be transformed once, right here; reaching for composition is usually right when the transformation itself is what needs to be named, stored, or passed elsewhere as a function.
A Realistic Pipeline Example
Pipelines earn their keep once a computation genuinely has several sequential steps. Consider building a list of the first n non-negative integers, squaring each one, and summing the results:
let rec range i j = if i > j then [] else i :: range (i + 1) j
let sum_of_squares n =
range 0 n
|> List.map (fun x -> x * x)
|> List.fold_left ( + ) 0
Each line of the pipeline corresponds to one clear step — build the range, square every element, sum them all — and reads in exactly the order those steps happen. The alternative, writing this with nested nested parentheses, would force exactly the same computation to be read inside-out instead:
let sum_of_squares n =
List.fold_left ( + ) 0 (List.map (fun x -> x * x) (range 0 n))
Both versions are equally correct; the piped version is generally easier to follow specifically because each transformation appears in the same left-to-right order it's actually applied.
Style Guidance: When to Reach for Each
|> earns its place once there are genuinely multiple sequential steps to a computation — two or more is usually the point where a pipeline starts reading more clearly than nested calls. For a single function application, plain juxtaposition (f x) or ordinary parentheses remain perfectly clear on their own, and reaching for |> or @@ for just one call adds a symbol without adding any real readability. @@ is most useful for shaving off one layer of otherwise-unavoidable parentheses in an isolated expression, rather than as a substitute for a full pipeline. A dedicated composition operator earns its place specifically when the combined transformation itself needs a name and needs to be reused as a function, not just applied once to a single value in place.
Key Takeaways
x |> fis exactlyf x; chaining several|>calls turns a deeply nested expression into a flat sequence that reads in the same order it executes.@@also removes parentheses, but preserves the original function-then-argument order rather than reversing it, and is right-associative rather than left-associative.- Piping applies functions immediately to a specific value; composing two functions produces a new, reusable function that hasn't been applied to anything yet — these are related but genuinely different operations.
- OCaml's standard library includes
|>and@@directly, but not a dedicated composition operator; defining one (commonly%or a custom named operator) is standard, idiomatic practice. - A pipeline is most valuable once a computation has several genuinely sequential steps; for a single function call, plain application or a single pair of parentheses is usually just as clear.
Found this helpful? Share the knowledge...
... or support this project to keep it alive!
Previous
Higher-Order Functions
Next
Currying & Partial Application
- What |> Actually Does
- @@: Application Without Parentheses, in the Original Order
- Piping Isn't the Same as Composing
- A Realistic Pipeline Example
- Style Guidance: When to Reach for Each
- Key Takeaways
