Left-to-Right Data Flow in F#
F# emphasizes readable, left-to-right data flow through two related tools: the pipe operator |> and the composition operators >> and <<. The pipe operator takes a value and feeds it as the last argument into a function, letting you chain a sequence of transformations in the order they actually happen. Composition instead combines two functions into a brand-new function without needing an initial value, which is useful for building reusable pipelines ahead of time.
Cricket analogy: The pipe operator is like a bowler's over being read ball by ball in the order it was bowled — data |> functionA |> functionB reads as 'first do this, then that,' just as a scorecard lists deliveries in the sequence they were bowled rather than backward.
The Pipe Operator |>
The pipe operator |> is defined simply as let (|>) x f = f x — it takes a value on the left and a function on the right, applying the function to the value. Because F# functions are curried, x |> f a b is equivalent to f a b x, inserting x as the final argument. This reads naturally left to right: numbers |> List.filter isEven |> List.map square |> List.sum describes the pipeline in the same order the computation happens, rather than nesting calls inside out like List.sum (List.map square (List.filter isEven numbers)).
Cricket analogy: Writing score |> applyPowerplayBonus |> applyBoundaryCount reads like a scorer processing a ball's outcome through successive rule adjustments in order, rather than nesting three rule-functions inside each other and losing track of sequence.
let isEven x = x % 2 = 0
let square x = x * x
// Pipe operator: left-to-right data flow
let result =
[1..10]
|> List.filter isEven
|> List.map square
|> List.sum
// Function composition: build a reusable pipeline first
let processNumber = square >> ((+) 1) // square, then add 1
printfn "%d" (processNumber 4) // 17
// Right-to-left composition with <<
let processNumber2 = ((+) 1) << square // same behavior as above
printfn "%d" (processNumber2 4) // 17
// Combining composition and piping
let webReady = List.filter isEven >> List.map square
let output = [1..6] |> webReadyFunction Composition with >> and <<
Composition combines two functions into one without applying either to a value yet. f >> g creates a new function that runs f first, then pipes its result into g — read left to right, matching |>. f << g runs g first, then f — read right to left, matching traditional mathematical notation g(f(x)) style composition. Composition is especially useful for building a named, reusable pipeline once, rather than repeating a chain of |> calls at every call site.
Cricket analogy: Composing bowlYorker >> checkWicket into a single named function attemptYorkerDismissal is like a bowling coach naming a drill that always runs the yorker delivery followed by the wicket check, so any player can reuse the whole sequence by one name.
Building Multi-Step Pipelines
Real F# code often mixes pipelining and composition: you compose small, well-named functions with >> to create meaningful building blocks, then pipe actual data through those blocks with |>. This keeps each function small and testable in isolation while keeping the overall data flow readable at the call site. A good rule of thumb is to compose functions that don't yet have data (building a strategy), and pipe functions once you have a concrete value to process.
Cricket analogy: A team composes selectPlaying11 >> assignBattingOrder ahead of the toss (no data yet — pure strategy), then on match day pipes the actual squad list through it: squad |> (selectPlaying11 >> assignBattingOrder), mirroring how composition builds strategy and piping applies it to real data.
The |> operator has very low precedence and is left-associative, so a |> f |> g runs f first, then g — always matching the visual left-to-right order. This is why F# code favors deeply chained pipelines over deeply nested function calls.
f >> g and f << g are easy to swap by mistake because they look similar. Remember: >> reads like an arrow pointing forward (f then g, left to right), while << reads like an arrow pointing backward (g then f, right to left, matching mathematical g(f(x)) notation). Swapping them compiles fine but silently reorders your pipeline.
- The pipe operator |> feeds a value as the last argument into a function, enabling left-to-right chains.
- x |> f a b is equivalent to f a b x thanks to currying.
- >> composes two functions left to right (f then g); << composes right to left (g then f).
- Composition builds a reusable function ahead of time; piping applies that function to concrete data.
- Chained pipelines read in the same order the computation actually happens, improving readability over nested calls.
- Combine small composed building blocks with piping to keep code both modular and readable.
Practice what you learned
1. What does `x |> f` evaluate to?
2. Given `let h = f >> g`, what happens when you call `h x`?
3. Why does `numbers |> List.filter isEven |> List.map square |> List.sum` read more clearly than the equivalent nested call?
4. What is the main practical difference between using >> versus repeated |> calls at every call site?
5. Which operator would you use to express 'apply square, then add 1' as a single composed function?
Was this page helpful?
You May Also Like
Higher-Order Functions in F#
Learn how F# treats functions as first-class values that can be passed as arguments, returned as results, and composed to build powerful, reusable data transformations.
Recursion and Tail Calls
Understand how F# uses recursion instead of mutable loops, and how tail-call optimization lets recursive functions run in constant stack space.
Computation Expressions
See how F#'s computation expressions, like the built-in option, async, and seq builders, plus custom ones, let you write sequential-looking code over effects like optionality, asynchrony, or laziness.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics