Almost every program ends up doing the same three things to lists, over and over: changing every item into something else, throwing away the items you don't want, and squeezing a pile of items down to one answer. You could hand-write a loop for each of these every single time. But there's a friendlier way — three little tools with friendly names: Transform, Keep, and Combine.
Here's a nice way to picture it. Think of your list like a tiny database table, and these three tools as the queries you run over it — you select new columns, you keep the rows that match a where clause, and you aggregate everything into a grand total. Same idea, but on any list you've got, right there in your code.
Each of these tools takes a function as a value — you hand it a little rule, and it does the looping for you. That trick has its own name: a higher-order function. You describe what you want done; the tool figures out how to walk the list.
Transform
Transform (you'll also hear it called map or select) takes a function and applies it to every single item, handing you back a brand-new list of the same length. Nothing is added, nothing is dropped — each item just gets made over. Want to double every number? Give Transform the rule "multiply by two" and it does the rest.
In F# the tool is called List.map. You pass it the rule and the list, and out comes the made-over list:
// Double every number
List.map (fun x -> x * 2) [1; 2; 3]
// => [2; 4; 6]
Three numbers go in, three numbers come out — each one transformed by the rule you supplied.
Watch it happen below. Every item in the list flows through the transform and lands as a new item on the other side. Notice that the count never changes — Transform is a one-to-one makeover.
- Transform (map)Apply a function to every item → a new list of the same length.
Keep
Keep (the classic names are filter or where) is the bouncer at the door. You give it a yes-or-no test, and it walks the list letting only the items that pass through. The ones that fail are simply left behind, so unlike Transform, the list can come out smaller than it went in.
In F# it's List.filter. Hand it a test that returns true or false, and it keeps the trues:
// Keep only the even numbers
List.filter (fun x -> x % 2 = 0) [1..6]
// => [2; 4; 6]
Six numbers go in, but only the three that pass the "is it even?" test make it out. This is your where clause: keep the rows that match.
In the animation, every item walks up to the test. The ones that match slip through to the result; the rest are turned away at the door. Same idea as picking out just the rows you care about from a table.
- Keep (filter)Only the items that pass the test make it through.
Combine
Combine (known as fold, reduce, or aggregate) is the one that boils a whole list down into a single value. Think of a running total: you start with a number, then walk the list adding each item to what you've got so far, until one final answer is all that's left.
In F# the tool is List.fold. You give it a combining rule, a starting value, and the list:
// Add everything up, starting from 0
List.fold (+) 0 [1; 2; 3; 4]
// => 10
It grabs the start (0), folds in 1, then 2, then 3, then 4, and the four numbers collapse into the grand total 10. That's your aggregate: one number that summarizes the whole list.
The scene shows the list folding inward, item by item, the running total growing as each one is absorbed — until the entire list has shrunk down to a single result.
- Combine (fold)Boil the whole list down to a single value.
Better than a loop
Why bother, when a plain loop can do all of this? Because these three tools say what you mean instead of how to do it. "Keep the even ones, then double them, then add them up" reads almost like plain English — no counters to manage, no off-by-one mistakes, no temporary list to remember to clear out. The intent is right there on the surface.
And the real magic is that they snap together. Because each one takes a list and (mostly) gives back a list, you can line them up so the output of one flows straight into the next. Stringing them together this way is called a pipeline, and it's where these little tools really sing:
[1..10]
|> List.filter (fun x -> x % 2 = 0) // Keep the evens
|> List.map (fun x -> x * x) // Transform: square them
|> List.fold (+) 0 // Combine: sum them up
// => 220
- KeepFilter: drops the odd numbers, so only 2 and 4 pass through.
- TransformMap: squares each survivor → 4 and 16, same count out as in.
- CombineFold: adds the squares into one final answer — 20.
When you catch yourself writing a loop over a list, pause and ask which of the three you're really doing. Changing every item? That's Transform. Picking some out? That's Keep. Reducing to one answer? That's Combine. Most loops are just one of these three wearing a disguise.